<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Edison Devadoss on Medium]]></title>
        <description><![CDATA[Stories by Edison Devadoss on Medium]]></description>
        <link>https://medium.com/@edisondevadoss?source=rss-5081eccbbef1------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*hg6RyZgjytZ6Zfy40k9I6A.jpeg</url>
            <title>Stories by Edison Devadoss on Medium</title>
            <link>https://medium.com/@edisondevadoss?source=rss-5081eccbbef1------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 09:33:51 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@edisondevadoss/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Introduction Prompt Evaluation]]></title>
            <link>https://edisondevadoss.medium.com/introduction-prompt-evaluation-df328e6aa4ea?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/df328e6aa4ea</guid>
            <category><![CDATA[pytest]]></category>
            <category><![CDATA[prompt-evaluation]]></category>
            <category><![CDATA[prompt-eval]]></category>
            <category><![CDATA[llm-testing]]></category>
            <category><![CDATA[deepeval]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Wed, 11 Nov 2026 09:59:10 GMT</pubDate>
            <atom:updated>2026-11-11T10:06:41.995Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi Friends, in this post, I will explain why prompt evaluation is needed in an application that makes LLM calls and show you an example of how to implement prompt evaluation using the DeepEval library in our Python project.</p><h4>What is Prompt Evaluation?</h4><p>Prompt Evaluation is the process of assessing how well a prompt meets defined quality criteria, using a consistent and repeatable method.</p><p>We all know LLM output is <em>non-deterministic</em>, unlike a programming language. For a normal function, we can know what the output will be based on the input we pass, so we can test this function with various cases(inputs) using a testing library such as pytest. Eventually, we know how this function will work under various scenarios.</p><p>How do we do such tests for LLM output with various inputs when LLM output is non-deterministic? LLM results are completely based on the params we have passed. Then how do we assess the quality of the params? — That is what Prompt Evaluation is for.</p><h4>DeepEval:</h4><p>DeepEval is one of the prompt evaluation libraries. Using this we can test LLM output like how we are doing unit-test using pytest. Not only that; it provides various features such as evaluating AI agents, voice agents, multi-turn chatbots, RAG pipelines, MCP systems, and other custom workflows.</p><h4>Evaluation Techniques &amp; Metrics:</h4><ol><li>Faithfulness — It extracts individual claims from the output and checks them against the context using LLM-as-judge. RAG pipelines must ensure the generator is not making up facts outside the provided source documents.</li><li>Answer Relevancy — It checks, based on the input, the number of relevant statements in the overall result (<em>Number of relevant statements / Total result</em>). It helps us catch off-topic drift.</li><li>G-Eval — Using G-Eval, we can check our custom criteria. We can check whether any of our specific custom rules worked as expected.</li></ol><h4>Golden test cases:</h4><p>We can define our own golden test cases (input and expected output). We can pass them to the test function @pytest.mark.parametrize and test all golden cases.</p><h4>Example:</h4><p>We can build a simple AI chat application and implement the above <strong>DeepEval</strong> techniques.</p><p><strong>Create a project directory</strong></p><pre>mkdir customer-support-ai<br>cd customer-support-ai</pre><p><strong>Create a virtual environment</strong></p><pre>python3 -m venv .eval_env<br>source .eval_env/bin/activate</pre><p><strong>Install required dependencies.</strong></p><p>First, create a requirements.txt file and add the following libs.</p><pre>google-genai<br>deepeval<br>fastapi<br>uvicorn<br>python-dotenv<br>scikit-learn<br>pytest</pre><p>Then, install everything.</p><pre>$ python -m pip install -r requirements.txt</pre><p><strong>Create environment variables</strong></p><p>Create a .env file in the project root directory.</p><pre>GOOGLE_API_KEY=your_gemini_api_key<br>GEMINI_MODEL=gemini-2.5-flash</pre><p>We can create a simple knowledge base folder that contains three simple text files. Our AI chatbot will answer based on these three files.</p><pre>$ mkdir app<br>$ cd app<br>$ mkdir knowledge_base<br>$ cd knowledge_base</pre><p>Under the knowledge_base folder, add these three files.</p><ol><li>cancellation_policy.txt</li></ol><pre>Cancellation Policy<br><br>1. Customers can cancel an order before it is shipped.<br><br>2. Orders that have already been shipped cannot be cancelled.<br><br>3. For shipped orders, customers must follow the return policy.</pre><p>2. return_policy.txt</p><pre>Return Policy<br><br>1. Customers can return eligible products within 30 days<br>   of delivery.<br><br>2. Products must be unused and in their original packaging.<br><br>3. Refunds are processed within 5-7 business days<br>   after the returned product is approved.</pre><p>3. shipping_policy.txt</p><pre>Shipping Policy<br><br>1. Standard delivery takes 3-5 business days.<br><br>2. Express delivery takes 1-2 business days.<br><br>3. Customers receive a tracking link after dispatch.</pre><p>Once we have added these three files, we can implement retriever.py to read these files based on the question (input) using a vector search approach.</p><p>Under the app folder, create retriever.py file.</p><pre>from pathlib import Path<br>from sklearn.feature_extraction.text import TfidfVectorizer<br>from sklearn.metrics.pairwise import cosine_similarity<br><br>BASE_DIR = Path(__file__).parent / &quot;knowledge_base&quot;<br><br>documents = [file.read_text() for file in BASE_DIR.glob(&quot;*.txt&quot;)]<br><br>vectorizer = TfidfVectorizer(stop_words=&quot;english&quot;)<br>document_vectors = vectorizer.fit_transform(documents)<br><br><br>def retrieve_documents(question, top_k=2):<br>    question_vector = vectorizer.transform([question])<br><br>    scores = cosine_similarity(question_vector, document_vectors)[0]<br><br>    top_indices = scores.argsort()[::-1][:top_k]<br><br>    return [documents[i] for i in top_indices]</pre><p>Instead of explaining every line of the above function (since we are focusing on DeepEval), I will provide a high-level explanation — it reads content from the document based on the question using a vector search approach.</p><p>Create the chatbot.py file under the app folder.</p><pre>import os<br>from dotenv import load_dotenv<br>from google import genai<br>from app.retriever import retrieve_documents<br><br>load_dotenv()<br><br>client = genai.Client(api_key=os.getenv(&quot;GOOGLE_API_KEY&quot;))<br>MODEL = os.getenv(&quot;GEMINI_MODEL&quot;)<br><br>def ask_customer_support(question):<br>    # 1. Retrieve relevant documents<br>    context = retrieve_documents(question)<br><br>    # 2. Prepare the prompt<br>    prompt = f&quot;&quot;&quot;<br>    You are a customer support assistant.<br><br>    Answer the customer&#39;s question using only<br>    the information in the provided context.<br><br>    If the answer is not available in the context,<br>    clearly say that you don&#39;t have enough information.<br><br>    Context:<br>    {context}<br><br>    Customer question:<br>    {question}<br><br>    Answer:<br>    &quot;&quot;&quot;<br><br>    # 3. Call Gemini<br>    response = client.models.generate_content(model=MODEL, contents=prompt)<br><br>    # 4. Return answer and retrieved context<br>    return {&quot;question&quot;: question, &quot;answer&quot;: response.text, &quot;retrieval_context&quot;: context}</pre><p>The above function first calls retrieve_documents function to get the relevant information from the document and set it as the context for the LLM prompt; then we add the question. Finally, we make an LLM call to get the answer to the question only based on the context.</p><p>Create main.py under the app folder.</p><pre>from fastapi import FastAPI<br>from app.chatbot import ask_customer_support<br><br>app = FastAPI()<br><br><br>@app.get(&quot;/chat&quot;)<br>def chat(question: str):<br>    return ask_customer_support(question)</pre><p>The above code just defines a chat API which calls ask_customer_support function to get the answer.</p><p>Project implementation is done. Now move to testing our LLM implementation.</p><h4>Test case implementation</h4><p>Now create a tests folder in the project root directory, then create a test_chatbot.pyfile.</p><p>First, import required libs for testing.</p><pre>import os<br>import pytest<br>from dotenv import load_dotenv<br><br>from deepeval import assert_test<br>from deepeval.metrics import FaithfulnessMetric, AnswerRelevancyMetric, GEval<br>from deepeval.test_case import LLMTestCase, SingleTurnParams<br>from deepeval.models import GeminiModel<br><br>from app.chatbot import ask_customer_support<br>from tests.test_cases import test_cases # We can define this later<br><br>load_dotenv()</pre><p>Here, a few items we need to note.</p><p>assert_test — This is for testing the defined test cases, as we can pass test cases and metrics as arguments.</p><p>FaithfulnessMetric, AnswerRelevancyMetric, GEval — These three items are the metrics we are using to evaluate the prompt.</p><p>GeminiModel — We are using GeminiModel as an LLM-as-a-judge.</p><p>LLMTestCase, SingleTurnParams — These two methods are for defining test cases.</p><h4>Define Metrics:</h4><pre>gemini_model = GeminiModel(<br>    model=os.getenv(&quot;GEMINI_MODEL&quot;), api_key=os.getenv(&quot;GOOGLE_API_KEY&quot;)<br>)<br><br>faithfulness = FaithfulnessMetric(threshold=0.7, model=gemini_model)<br><br>answer_relevency = AnswerRelevancyMetric(threshold=0.7, model=gemini_model)<br><br>policy_compliance = GEval(<br>    name=&quot;Policy Compliance&quot;,<br>    criteria=&quot;&quot;&quot;<br>         Evaluate whether the assistant follows the company policies.<br><br>    The assistant must not:<br>    1. Invent return or refund policies.<br>    2. Promise exceptions not mentioned in the context.<br>    3. Provide information that contradicts the policy.<br><br>    Give a high score when the answer follows these rules.<br>    &quot;&quot;&quot;,<br>    evaluation_params=[<br>        SingleTurnParams.INPUT,<br>        SingleTurnParams.ACTUAL_OUTPUT,<br>        SingleTurnParams.RETRIEVAL_CONTEXT,<br>        SingleTurnParams.EXPECTED_OUTPUT,<br>    ],<br>    threshold=0.7,<br>    model=gemini_model,<br>)</pre><p>In the above code, we have defined <em>faithfulness</em>, <em>answer relevancy</em>, and <em>policy compliance</em> metrics. In each item, we have passed a <em>threshold</em> and a <em>model</em>.</p><h4>Define Golden test cases:</h4><p>Under the tests folder, create a test_cases.py file with the following code as the sample test cases (input and expected behaviour).</p><pre>test_cases = [<br>    {<br>        &quot;input&quot;: &quot;Can I return a product after 20 days?&quot;,<br>        &quot;expected_behavior&quot;: &quot;Explain the 30-day return policy.&quot;,<br>    },<br>    {<br>        &quot;input&quot;: &quot;Can I return a product after 45 days?&quot;,<br>        &quot;expected_behavior&quot;: &quot;Explain that the return period is 30 days.&quot;,<br>    },<br>    {<br>        &quot;input&quot;: &quot;How long does shipping take?&quot;,<br>        &quot;expected_behavior&quot;: &quot;Explain standard and express delivery times.&quot;,<br>    },<br>    {<br>        &quot;input&quot;: &quot;Can I cancel an order after shipping?&quot;,<br>        &quot;expected_behavior&quot;: &quot;Explain that shipped orders cannot be cancelled.&quot;,<br>    },<br>    {<br>        &quot;input&quot;: &quot;Do you sell mobile phones?&quot;,<br>        &quot;expected_behavior&quot;: &quot;Do not invent product information.&quot;,<br>    },<br>]</pre><h4>Define a test case and a testing function</h4><pre>@pytest.mark.parametrize(&quot;test_data&quot;, test_cases)<br>def test_return_policy(test_data):<br>    result = ask_customer_support(test_data[&quot;input&quot;])<br><br>    test_case = LLMTestCase(<br>        input=result[&quot;question&quot;],<br>        actual_output=result[&quot;answer&quot;],<br>        expected_output=test_data[&quot;expected_behavior&quot;],<br>        retrieval_context=result[&quot;retrieval_context&quot;],<br>    )<br><br>    assert_test(test_case, [faithfulness, answer_relevency, policy_compliance])</pre><p>In the above code, using parameterize approach, we are passing those test_cases to the test_return_policy function; it runs each test case.</p><p>result = ask_customer_support(test_data[“input”]) — This statement takes input from the test_case and passes it to the ask_customer_support function, which gets answers from the LLM.</p><pre>test_case = LLMTestCase(<br>        input=result[&quot;question&quot;],<br>        actual_output=result[&quot;answer&quot;],<br>        expected_output=test_data[&quot;expected_behavior&quot;],<br>        retrieval_context=result[&quot;retrieval_context&quot;],<br>    )<br> assert_test(test_case, [faithfulness, answer_relevency, policy_compliance])</pre><p>Once we get the result, we can pass it to the LLMTestCase method. The result dictionary has question, answer, and retrieval_contextalong with this, we are passing expected_behavior from test_case .</p><p>assert_test(test_case, [faithfulness, answer_relevency, policy_compliance]) — This is the final statement to test and give the result. We passed test_case as the first argument, then metrics as the second argument.</p><p>Now run the test cases:</p><pre>$ deepeval test run tests/test_chatbot.py</pre><h4>Output is:</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x-73i_9PirHi6ddzqJtURw.png" /></figure><p>Note: Due to the API rate limit, I kept only one test case for this test run.</p><p>The screenshot shows the results of the test cases for the defined metrics. This is how we can test the quality and consistency of the LLM implementation in our application.</p><p>Thank you for reading. Have a nice day!</p><h4>Reference Links:</h4><p><a href="https://deepeval.com/docs/introduction">https://deepeval.com/docs/introduction</a></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FHAoKJT3af7Y%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DHAoKJT3af7Y&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FHAoKJT3af7Y%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/db432099d605143b489dc41716c02d9f/href">https://medium.com/media/db432099d605143b489dc41716c02d9f/href</a></iframe><p>Source Code:</p><ul><li><a href="https://github.com/EdisonDevadoss/deep_eval_customer_support_ai">GitHub - EdisonDevadoss/deep_eval_customer_support_ai</a></li><li><a href="https://github.com/EdisonDevadoss/deep_eval_tutorial">GitHub - EdisonDevadoss/deep_eval_tutorial</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=df328e6aa4ea" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fundamentals of pytest]]></title>
            <link>https://edisondevadoss.medium.com/fundamentals-of-pytest-821f288e6674?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/821f288e6674</guid>
            <category><![CDATA[python-unit-testing]]></category>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[pytest]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Wed, 07 Oct 2026 11:17:52 GMT</pubDate>
            <atom:updated>2026-10-07T11:17:52.917Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi friends, in this post, I am going to explain the basic features of pytest — a Python testing framework.</p><p>I have been working on a few FastAPI projects where I need to write unit test cases, but I am using Claude-code to write those test cases, run the test cases, and ensure all the items are green (successfully run ). I feel I am not understanding how pytest works and the nitty-gritty of the library.</p><p>Then I started to watch a <a href="https://www.youtube.com/watch?v=cHYq1MRoyI0">YouTube video (freeCodeCamp course)</a> learnt basics of the pytest library and how to use it to write test cases. This is what I used to do before AI-assisted coding.</p><h4>What is pytest</h4><p>It is the most widely used Python testing framework, which we can use with any backend library, whether it is FastAPI, Django or Flask, or a plain Python script; pytest works smoothly.</p><h4>Core Features:</h4><p><strong><em>Auto-discovery of tests: </em></strong>Pytest has the ability to pick up tests based on how we name them. The pytest command automatically discovers all tests.</p><p><strong><em>Rich Assertion Introspection: </em></strong>Pytest automatically provide detailed and readable error messages when a plain assert statement fails.</p><p><strong><em>Support parameterised and fixture-based testing: </em></strong>Using the parameterisation approach, we can run a test function multiple times with different inputs. Fixtures set up the environment for test cases that need to run (e.g., open a database connection before running the test cases).</p><h4>Installation and setup:</h4><p>First, we need to create a virtual environment</p><pre>python3 -m venv venv<br>source venv/bin/activate</pre><p>Then, install ptyest</p><pre>$ pip install -U pytest<br>$ pytest --version</pre><p>We can take a simple project as an example to write basic tests.</p><p>First, we can write test cases for a simple function</p><pre># my_functions.py<br>def add(number_one, number_two):<br>    return number_one + number_two<br><br><br>def divide(number_one, number_two):<br>    if number_two == 0:<br>        raise ValueError<br><br>    return number_one / number_two</pre><p>Now we can write test cases for the above two functions. It is just adding and dividing numbers.</p><pre>$ mkdir tests<br>$ cd tests<br>$ touch test_my_functions.py</pre><p>First, import the necessary items in the test_my_functions.py file</p><pre>import pytest<br>import time<br>import source.my_functions as my_functions<br></pre><p>Now we can write test cases for the add function</p><pre>def test_add():<br>    result = my_functions.add(number_one=1, number_two=4)<br><br>    assert result == 5</pre><p>The above one is simple; just create a function test_add and call the my_functions.add inside the function and get the result. Then, using assert, compare the result. If it matches, the test case passes.</p><h4><strong>Run the test cases</strong></h4><pre>$  python -m pytest tests/test_my_functions.py</pre><p>This is a simple example of how to write unit test cases for a simple function.</p><p><strong><em>Write test cases for a Class:</em></strong></p><p>Create a file shape.py and add the lines of code.</p><pre>import math<br><br>class Shape:<br><br>    def area(self):<br>        pass<br><br>    def perimeter(self):<br>        pass<br><br><br>class Circle(Shape):<br><br>    def __init__(self, radius):<br>        self.radius = radius<br><br>    def area(self):<br>        return math.pi * self.radius**2<br><br>    def perimeter(self):<br>        return 2 * math.pi * self.radius</pre><p>It just creates a Circle, and using the Shape class, we can create multiple shapes later.</p><p>Now I am creating a test case file for the Circle class under the tests folder: test_circle.py</p><pre>import pytest<br>import source.shapes as shapes<br>import math<br><br><br>class TestCircle:<br><br>    def setup_method(self, method):<br>        print(f&quot;Setting up {method}&quot;)<br>        self.circle = shapes.Circle(10)<br><br>    def teardown_method(self, method):<br>        print(f&quot;Teardown {method}&quot;)<br><br>        del self.circle<br><br>    def test_radius(self):<br>        assert self.circle.area() == math.pi * self.circle.radius**2<br><br>    def test_one(self):<br>        assert True<br><br>    def test_perimeter(self):<br>        result = self.circle.perimeter()<br>        expectec = 2 * math.pi * self.circle.radius<br><br>        assert result == expectec<br><br>    def test_not_same_area_rectangle(self, my_rectangle):<br>        assert self.circle.area() != my_rectangle.area()</pre><p>I have provided my entire test suite for the Circle class. Here, a couple of things we need to notice: there are two functions it has, but it nothing to do with testing the Circle class, but it is essential. Those two functions are setup_method() and teardown_method() . These two functions are part of pytest lifecycle methods. They will be automatically called before and after each test method is called.</p><p><strong><em>Run the test cases</em></strong></p><pre>$ python -m pytest tests/test_circle.py -s</pre><p>s — This option ensure to see the print statements.</p><p><strong><em>See the output below:</em></strong></p><pre>$ python -m pytest tests/test_circle.py -s<br>========================================================================================== test session starts ===========================================================================================<br>platform darwin -- Python 3.13.11, pytest-9.1.1, pluggy-1.6.0<br>rootdir: /Users/mahendran/Edison/learnings/python/fastapi/pytest-fcc<br>collected 4 items                                                                                                                                                                                        <br><br>tests/test_circle.py Setting up &lt;bound method TestCircle.test_radius of &lt;test_circle.TestCircle object at 0x101700e10&gt;&gt;<br>.Teardown &lt;bound method TestCircle.test_radius of &lt;test_circle.TestCircle object at 0x101700e10&gt;&gt;<br>Setting up &lt;bound method TestCircle.test_one of &lt;test_circle.TestCircle object at 0x101701590&gt;&gt;<br>.Teardown &lt;bound method TestCircle.test_one of &lt;test_circle.TestCircle object at 0x101701590&gt;&gt;<br>Setting up &lt;bound method TestCircle.test_perimeter of &lt;test_circle.TestCircle object at 0x10167a060&gt;&gt;<br>.Teardown &lt;bound method TestCircle.test_perimeter of &lt;test_circle.TestCircle object at 0x10167a060&gt;&gt;<br>Setting up &lt;bound method TestCircle.test_not_same_area_rectangle of &lt;test_circle.TestCircle object at 0x10167a3f0&gt;&gt;<br>.Teardown &lt;bound method TestCircle.test_not_same_area_rectangle of &lt;test_circle.TestCircle object at 0x10167a3f0&gt;&gt;<br><br><br>=========================================================================================== 4 passed in 0.00s ============================================================================================</pre><p>In the output above, we can see that setup_method is called before each function is called, and teardown_method is called after each function is called.</p><h4>Fixture</h4><p>Now we will see another important feature: Fixture.</p><p>A fixture is for setting up the environment for test cases that we need to run. If we have common fixtures and they are going to be used in multiple places, we can create a conftest.py file and write common fixtures there. We do not need to import the conftest.py file in each of our test case files. It automatically finds the conftest file.</p><p>First, we can add one more class, Rectangle, in the shape.py file</p><pre>class Rectangle(Shape):<br><br>    def __init__(self, length, width):<br>        self.length = length<br>        self.width = width<br><br>    def __eq__(self, other):<br>        if not isinstance(other, Rectangle):<br>            return False<br><br>        return self.width == other.width and self.length == other.length<br><br>    def area(self):<br>        return self.length * self.width<br><br>    def perimeter(self):<br>        return (self.length * 2) + (self.width * 2)</pre><p>Create a conftest.py file</p><pre>$ cd tests<br>$ touch conftest.py</pre><p>Now we add fixtures for rectangle creation.</p><pre># conftest.py<br>import pytest<br>import source.shapes as shapes<br><br><br>@pytest.fixture<br>def my_rectangle():<br>    return shapes.Rectangle(10, 20)<br><br><br>@pytest.fixture<br>def weird_rectangle():<br>    return shapes.Rectangle(5, 6)</pre><p>Here, I have created fixtures that I can use in other places.</p><p>Create a test_rectangle.py file</p><pre># test_rectangle.py<br><br>import pytest<br><br>def test_area(my_rectangle):<br>    assert my_rectangle.area() == 10 * 20<br><br><br>def test_perimeter(my_rectangle):<br><br>    assert my_rectangle.perimeter() == (10 * 2) + (20 * 2)<br><br><br>def test_not_equal(my_rectangle, weird_rectangle):<br>    assert my_rectangle != weird_rectangle</pre><p>Here, this test file automatically reads the conftest.py, and it automatically passes the my_rectangle function as a parameter. Using this approach, we can reuse common functions.</p><p>Thank you for reading. Have a nice day!</p><p><strong><em>Reference Links:</em></strong></p><p>GitHub source code: <a href="https://github.com/EdisonDevadoss/pytest-fundamentals">https://github.com/EdisonDevadoss/pytest-fundamentals</a></p><p>Video Reference: <a href="https://www.youtube.com/watch?v=cHYq1MRoyI0">https://www.youtube.com/watch?v=cHYq1MRoyI0</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=821f288e6674" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Langchain RAG-Agent]]></title>
            <link>https://edisondevadoss.medium.com/langchain-rag-agent-2b7d9f715ca7?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/2b7d9f715ca7</guid>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[rag-agent]]></category>
            <category><![CDATA[agentic-rag]]></category>
            <category><![CDATA[ai-tool-calling]]></category>
            <category><![CDATA[langchain-agents]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Wed, 31 Dec 2025 15:48:28 GMT</pubDate>
            <atom:updated>2025-12-31T15:48:28.135Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi friends, in this post, I will explain how to build an RAG agent using one of the famous LLM (Large Language Model) frameworks, Langchain.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*1I4n5famkjUnFPrPiH_6CA.png" /><figcaption>canva.com</figcaption></figure><h4>What is RAG:</h4><p>The RAG (Retrieval-Augmented Generation) fetches external and up-to-date information and gives it to the LLM for answering users’ questions.</p><p>Without RAG, LLM answers only from their model-trained data. If the user asks anything beyond that, the LLM cannot answer such questions. But, RAG, as a knowledge base, provides those capabilities to the LLM.</p><p><strong>Then, what is RAG-Agent?</strong></p><blockquote>A RAG (Retrieval-Augmented Generation) Agent is an AI system that combines a Large Language Model (LLM) with an external knowledge base, allowing it to retrieve factual, up-to-date information before generating an answer, making responses more accurate, context-aware, and less prone to hallucination.</blockquote><p>In the traditional RAG system, whenever a user ask question, we have to embed (vectorise) that question, then using that embedding we need to find relevant matches in the vector database. Once we get the relevant matches from the vector database, then we need to pass them to the LLM. Based on the relevant matches, LLM will answer users’ questions.</p><p>But, in RAG-Agent, we do not need to embed manually, find relevant matches from the vector database and pass them to the LLM.</p><p>I will show how to implement a simple RAG agent. In this project, I am using FastAPI and a few LangChain libraries.</p><h4>Step 1: Project creation &amp; installation</h4><pre>$ mkdir rag_agent<br>$ cd rag_agent</pre><p>Virtual environment creation and activation (For Mac and Ubuntu, you can follow the below one for venv creation, but for Windows, it slightly changes)</p><pre>$ python3 -m venv .venv<br>$ source .venv/bin/activate</pre><p><strong>Package installation</strong></p><pre>$ touch requirement.txt</pre><p>And add the following packages to the <strong>requirements.txt</strong> file</p><pre>python-dotenv<br>fastapi<br>langchain[anthropic]<br>langchain<br>langchain_community<br>langchain-text-splitters <br>pypdf<br>langchain-huggingface<br>langchain-chroma</pre><h4>Step 2: Project and route configuration</h4><pre>$ touch main.py</pre><p>Add the following configuration to the <strong>main.py</strong> file.</p><pre>from fastapi import FastAPI<br>from fastapi.middleware.cors import CORSMiddleware<br>from dotenv import load_dotenv<br><br>import my_profile<br><br>load_dotenv()<br>app = FastAPI()<br><br>app.add_middleware(<br>    CORSMiddleware,<br>    allow_origins=[&quot;*&quot;],<br>    allow_credentials=True,<br>    allow_methods=[&quot;*&quot;],<br>    allow_headers=[&quot;*&quot;],<br>)<br><br>app.include_router(my_profile.router)<br><br>@app.get(&quot;/&quot;)<br>def root():<br>    return { &quot;hello&quot;: &quot;World&quot; }<br></pre><p>Run the application</p><pre>$ uvicorn main:app --reload</pre><p>If you have uvicorn, we can run using that library. Now we see the running application on <a href="http://localhost:8000/">http://localhost:8000</a>/</p><h4><strong>Step3: Langchain configuration</strong></h4><p>Create the <strong>my_profile.py</strong> file for our sample application and import the necessary packages.</p><pre>from fastapi.routing import APIRouter<br>from langchain.agents import create_agent<br>from langchain_community.document_loaders import PyPDFLoader<br>from langchain.messages import HumanMessage, AIMessage<br>from langchain_text_splitters import RecursiveCharacterTextSplitter<br>from langchain.tools import tool<br>from langchain_huggingface import HuggingFaceEmbeddings<br>from langchain_chroma import Chroma<br><br>router = APIRouter(tags=[&quot;my_profile&quot;], prefix=&quot;/my_profile&quot;) </pre><p><strong>Configure embedding</strong></p><pre>embeddings = HuggingFaceEmbeddings(model_name=&quot;sentence-transformers/all-MiniLM-L6-v2&quot;)</pre><p>HuggingFaceEmbeddings is a Langchain wrapper class which connects with an embedding model. sentence-transformers/all-MiniLM-L6-v2 — It is an embedding model which we are using for text embedding.</p><p><strong>Configure vector store</strong></p><pre>vector_store = Chroma(collection_name=&quot;my_profile&quot;, embedding_function=embeddings, persist_directory=&quot;./chroma_db&quot;)</pre><p>The above one is my vector database configuration. I have chosen Chroma DB as my vector database. Here, one thing we have to note is <strong>embedding_function=embeddings — </strong>We have configured the embedding model when defining the vector store. So, it automatically converts our text into an embedding and stores it.</p><p><strong>Defining tools</strong></p><pre>@tool(response_format=&quot;content_and_artifact&quot;)<br>def retrieve_context(query: str):<br>    &quot;&quot;&quot;Retrieve information to help answer a query.&quot;&quot;&quot;<br><br>    retrieved_docs = vector_store.similarity_search(query, k=2)<br>    serialized = &quot;\n\n&quot;.join((f&quot;Source: {doc.metadata}\nContent: {doc.page_content}&quot;)<br>                for doc in retrieved_docs<br>            )<br>    return serialized, retrieved_docs<br><br><br>def send_connection_email(sender_name: str, sender_email: str, message: str):<br>    &quot;&quot;&quot;Send an email to Edison Devadoss when someone wants to connect.&quot;&quot;&quot;<br>    try:<br><br>       print(&quot;sender mail&quot;, sender_email)<br>       print(&quot;sender name&quot;, sender_name)<br>       print(&quot;message&quot;, message)<br>        <br>    except Exception as e:<br>        return f&quot;Error sending email: {str(e)}&quot;<br><br>tools = [retrieve_context, send_connection_email]</pre><p>Here, I have configured two tools, one for getting relevant matches from the vector store (retrieve_context). Another one is for sending an email message (Not fully implemented), but when it collected required details from the user, this function will be called. We can see the user’s email, name and message in the logs.</p><p>In this tool function writing doc_string is important; it helps LLM to identify when a particular function should be called.</p><h4>Create agent</h4><pre>agent = create_agent(<br>    model=&quot;claude-sonnet-4-5-20250929&quot;,<br>    tools=tools,<br>    system_prompt=&quot;&quot;&quot;You are Edison Devadoss&#39;s intelligent personal assistant. Your primary role is to provide information about Edison and facilitate connection requests.<br><br>## Your Responsibilities:<br><br>### 1. Answer Questions About Edison<br>- Respond ONLY to questions about Edison Devadoss using the knowledge base provided<br>- Use the retrive_context tool to search for relevant information<br>- Provide accurate, helpful answers based on Edison&#39;s professional profile, skills, experience, and background<br>- If information is not available in the knowledge base, politely inform the user that you don&#39;t have that information<br><br>### 2. Handle Connection Requests<br>When someone wants to connect with Edison:<br>- Collect their information in this order:<br>  * Full name<br>  * Email address<br>  * Brief reason for wanting to connect (what they&#39;d like to discuss or collaborate on)<br>- Use the send_connection_email tool only after you have all three pieces of information<br>- Confirm the email has been sent successfully<br><br>## Important Guidelines:<br><br>- Stay focused on Edison&#39;s professional context only<br>- Do NOT answer general knowledge questions, trivia, or topics unrelated to Edison<br>- If asked about unrelated topics, politely redirect: &quot;I&#39;m specifically designed to help with questions about Edison Devadoss. How can I assist you with information about Edison?&quot;<br>- Be professional, friendly, and concise in your responses<br>- Always verify information through the knowledge base before answering<br>- If someone asks something unrelated to Edison, do not attempt to answer—instead ask if they have questions about Edison<br><br>## Response Style:<br>- Be conversational yet professional<br>- Use a friendly tone<br>- Keep responses clear and concise<br>- Highlight Edison&#39;s relevant skills and experience when appropriate<br>&quot;&quot;&quot;<br>)</pre><p>In the above code, we defined an agent with the following options.</p><ol><li>Model</li><li>Tools</li><li>System prompt</li></ol><p>We configured prefered LLM model, our tool function and system prompt.</p><h4>Step 4: Store document in the Vector store:</h4><pre>@router.get(&quot;/&quot;)<br>def update_profile():<br>    loader = PyPDFLoader(<br>       &quot;https://edisondevadoss.in/static/media/edison-resume.ce0cde934d768621e8f9.pdf&quot;<br>    )<br><br>    docs = loader.load()<br>    text_spliter= RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)<br>    all_splits = text_spliter.split_documents(docs)<br>    document_ids = vector_store.add_documents(documents=all_splits)<br>   <br>    return {&quot;message&quot;: &quot;Profile updated successfully&quot;}</pre><p>I have created an API endpoint (It is supposed to be a POST method), and whenever a user hits that endpoint, it reads my resume from the URL (It is a pdf document).</p><p>Then, using langchain_text_splitters, we split the texts. After splited we store them in the vector_store. It is stored as an embedding.</p><h4>Step 5: Answers user’s question</h4><pre><br># In production, use a database to persist conversations<br>conversation_history = []<br><br>@router.get(&quot;/ask&quot;)<br>def answer(query_txt: str):<br>    result = None<br>    conversation_history.append(HumanMessage(content=query_txt))<br><br>    response = agent.invoke(<br>        {&quot;messages&quot;: conversation_history}<br>    )<br>    # Get the last message which should be the assistant&#39;s response<br>    if response and &quot;messages&quot; in response:<br>        last_message = response[&quot;messages&quot;][-1]<br>        result = last_message.content<br>        conversation_history.append(AIMessage(content=result))<br>    <br>    return {&quot;answer&quot;: result}</pre><p>In the above code, first we have a conversation_history list where we are storing conversion, both the user’s question and the LLM’s response. If we want to establish a connection between one message and another, we need to maintain conversation history. So that LLM will consider the relevance of the current query with the previous list of messages. It helps LLM to understand the context better.</p><p>Whever user sends a query, we append it to the conversation_history list and pass the entire conversation_history to the agent’s invoke function. This will call the required tool and fetch the result from it, and give it to the LLM to answer users&#39; questions.</p><p><strong>Output:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aWxnrzZqDqS8oowiDbNgdQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hfJIi1DYDq84mxlzddpJbQ.png" /></figure><p>In these two screenshots, the first one is storing docs in a vector database. The second one is after storing it, we are querying the RAG and fetching information.</p><p>Thank you for reading. Have a nice day!</p><p><strong>Reference Link</strong></p><p><a href="https://docs.langchain.com/oss/python/langchain/rag#build-a-rag-agent-with-langchain">Build a RAG agent with LangChain - Docs by LangChain</a></p><p>Github Source Code: <a href="https://github.com/EdisonDevadoss/LangChain-RAG-Agent">https://github.com/EdisonDevadoss/LangChain-RAG-Agent</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2b7d9f715ca7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LLM’s fundamentals, capabilities, and limitations]]></title>
            <link>https://edisondevadoss.medium.com/llms-fundamentals-capabilities-and-limitations-8a75d408f16a?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/8a75d408f16a</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[anthropic-claude]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Wed, 24 Dec 2025 13:10:08 GMT</pubDate>
            <atom:updated>2025-12-24T13:10:08.665Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*TzrgLC-AUM-0SVZ_YnNgqg.png" /><figcaption><a href="https://www.canva.com/">https://www.canva.com</a></figcaption></figure><p>Hi, friends, I’m going to address the fundamentals, capabilities, and limitations of LLM in this post, which I learned in an <a href="https://anthropic.skilljar.com/ai-fluency-framework-foundations">Anthropic course.</a></p><h4>Generative AI fundamentals:</h4><p>We are all using AI every day without understanding what happend behine the scenes.</p><blockquote>Generative AI refers to an Artificial Intelligence system that can create new content, rather than evaluate existing data.</blockquote><h4>Gen-AI has three pillars:</h4><ol><li>Algorithms — Natural Networks, Transformers Architecture</li><li>Data — Articles, websites, code and multimodal content</li><li>Computations — GPUS, TPUS, Computing clusters, etc.</li></ol><p>Any LLM model consists of the three pillars mentioned above.</p><p><strong>How to use the LLM:</strong> We can use an LLM model like delegating a task to a new employee and letting them do it on our behalf.</p><h4>Limitation:</h4><p>Below are the limitations of any LLM model:</p><ul><li><strong>Knowledge cut-off date</strong> — As long as the model is trained, it contains data only up to that date. It will not have the current date’s (latest) data.</li><li><strong>Incorrect Information </strong>— The model might have trained with incorrect data.</li><li><strong>Hallucination</strong> — If an AI model is trained on a dataset comprising biased or unrepresentative data, it may hallucinate patterns or features that reflect these biases.</li><li><strong>AI Context Window </strong>— It models “Working Memory”, defining the maximum amount of text (input prompts, previous conversation turns, documents) it can process and remember at one time, measured in tokens (words or parts of words).</li></ul><p>Now, I will explain the framework that helps us to bring the best output from LLM.</p><p>There are four competencies (4Ds):</p><ul><li>Delegation</li><li>Description</li><li>Discernment</li><li>Diligence</li></ul><p>These four components are important in dealing with AI. I will explain each of these.</p><h4>Delegation:</h4><p>Before using the LLM model, we should know three things.</p><ol><li>What needs to be done?</li><li>What is AI going to do?</li><li>What are we going to do?</li></ol><p>Once we know what works need to be done. Then, we should consider which task AI is going to do, and which task we are going to do. To do these, we should have project awareness and platform awareness (the capability of AI).</p><p>A person who has good knowledge in their field can drive more from AI</p><h4>Description:</h4><p>It is communicating with LLM. It is more than just a prompt.</p><p>The quality of LLM output depends on how clearly we describe what we want.</p><p>There are effective prompting techniques:</p><p>It is like explaining a task to a helpful new employee who is eager to assist us.</p><p><strong>Fundamental Prompting Tips:</strong></p><ul><li>Provide Context</li><li>Proivde example</li><li>Specifying output constraints</li><li>Breakdown complex tasks</li><li>Give the AI space to think.</li><li>Define role</li></ul><p>We can ask AI to frame an effective prompt. We can use that prompt.</p><h4>Discernment:</h4><p>It is evaluating what we receive from LLM, whether it meets our needs or not.</p><p>There are three kinds of discrement:</p><ol><li>Product discernment — Evaluating the quality of AI Output</li><li>Process discernment — Assessing how AI approach the task</li><li>Performance discernment — Evaluating how AI behaved during the interaction itself.</li></ol><h4>Diligence:</h4><p>It focuses mostly on ethics and safety aspects. There are three kinds of diligence.</p><ol><li>Creation Diligence — We should be thoughtful about which AI systems we use and how we engage with them.</li><li>Transparency Diligence — We should be aware of AI’s role in our work</li><li>Deployment Diligence — We should take ownership of AI-assisted outputs we share with others.</li></ol><p>If we consider these four competencies when we interact with LLM, we can get the best results from it, and we should be aware of its capabilities and limitations.</p><p>Thank you for reading. Have a nice day!</p><p><strong>Reference Link</strong></p><p><a href="https://anthropic.skilljar.com/ai-fluency-framework-foundations">https://anthropic.skilljar.com/ai-fluency-framework-foundations</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8a75d408f16a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Web Components with Storybook]]></title>
            <link>https://edisondevadoss.medium.com/web-components-with-storybook-8644e9d13d42?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/8644e9d13d42</guid>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[webcomponentsstorybook]]></category>
            <category><![CDATA[storybook]]></category>
            <category><![CDATA[lit]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Tue, 10 Jun 2025 10:31:50 GMT</pubDate>
            <atom:updated>2025-06-10T10:31:50.007Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi friends, In this post, I would like to share how to configure to web components.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x0dB0jwlDgaWKkJR7LA5eQ.png" /><figcaption><a href="https://www.photopea.com/">https://www.photopea.com/</a></figcaption></figure><p>This post is an extension of the previous blog — how to create and publish web components in the npm registry. <a href="https://edisondevadoss.medium.com/web-components-introduction-5007ee4908e6">Click here to read</a>.</p><h4>Why do we need a storybook?</h4><p>Over the years, frontend development has become complicated. There are many things a frontend developer needs to consider while developing a frontend application — Responsive design, different browsers support, application local state management, accessibility, and performance.</p><p>The modern JavaScript libraries and frameworks follow a component-driven approach (React, Angular, etc). In the component-driven approach, it breaks down complex UI into small, maintainable components.</p><p>But, there is a caveat in this approach, as the application grows, a number of components also grow. It becomes unmaintainable, and if we want to test the component, we need to run the application and go to a particular page and test it. It is a complicated work over time.</p><p>Storybook solves those problems — it isolates UI, now every piece of UI is a component, it provides a separate interface to render UI components, and we do not need to run the overall application.</p><p>As we render our component separately by storybook, we can change various props, mock data and fake events to know how our component works.</p><p>Storybook automatically generate documentation for each component. It provides testing features to write test cases and test components.</p><blockquote>Storybook is packaged as a small, development-only, <a href="https://bradfrost.com/blog/post/a-frontend-workshop-environment/">workshop</a> that lives alongside your app. It provides an isolated iframe to render components without interference from app business logic and context. That helps you focus development on each variation of a component, even the hard-to-reach edge cases.</blockquote><h4>How do we add a story to our web component project?</h4><p>I already created a web component project using <strong>Vite.</strong></p><p>You can access the repo <a href="https://github.com/EdisonDevadoss/lit-webcomponent-tutorial">here and clone it</a>, then do the following steps to set up Storybook.</p><p>After cloning the project and move to the project folder.</p><h4>Install Storybook and initialise</h4><pre>$ npx storybook@latest init</pre><p>The above command installs the required dependencies and init makes changes in the project folder. It added .storybook and stories folder.</p><p>.storybook — The folder contains all the configuration details of the storybook.</p><p>stories — Where we write stories for each UI component. This folder comes pre-defined UI and their stories. It should look like the screenshot below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/689/1*tytSJ1q4zik_ICZnYo2cZQ.png" /></figure><p>Run the storybook</p><pre>$ npm run storybook</pre><p>It will open the <a href="http://localhost:6006/">http://localhost:6006/</a> URL in our browser, where we find the UI description and the playground for our UI Components. We can play around with it.</p><p>We can remove unwanted UI and their stories.</p><h4>Write a storybook for our web component</h4><pre>import { LitElement, html, css } from &#39;lit&#39;;<br>import { customElement, property, state } from &#39;lit/decorators.js&#39;;<br>import { classMap } from &#39;lit/directives/class-map.js&#39;;<br><br>@customElement(&#39;word-viewer&#39;)<br>export class WordView extends LitElement {<br>  static styles = css`<br>    :host {<br>      color: black;<br>      font-size: 30px;<br>      cursor: pointer;<br>      padding 2rem;<br>      display: inline-block;<br>    }<br><br>    pre{<br>     background: white;<br>     padding: 0.2rem;<br>    }<br>  `;<br><br>  @state() private idx = 0;<br><br>  @state() private playDirection = 1;<br><br>  @property() words: string = &#39;&#39;;<br><br>  private intervalTimer?: number;<br><br>  override connectedCallback() {<br>    super.connectedCallback();<br>    this.intervalTimer = setInterval(() =&gt; {<br>      this.tickToNextWord();<br>    }, 1000);<br>  }<br><br>  override disconnectedCallback() {<br>    super.disconnectedCallback();<br>    clearInterval(this.intervalTimer);<br>    this.intervalTimer = undefined;<br>  }<br><br>  render() {<br>    const splitedWords = this.words.split(&#39; &#39;);<br>    return html`&lt;pre<br>      @click=${this.switchPlayDirection}<br>      class=&quot;${classMap({ backwards: this.playDirection === -1 })}&quot;<br>    &gt;<br>    ${splitedWords[<br>        ((this.idx % splitedWords.length) + splitedWords.length) %<br>          splitedWords.length<br>      ]}<br>    &lt;/pre<br>    &gt;`;<br>  }<br><br>  tickToNextWord() {<br>    this.idx += this.playDirection;<br>  }<br><br>  switchPlayDirection() {<br>    this.playDirection *= -1;<br>  }<br>}<br><br>declare global {<br>  interface HTMLElementTagNameMap {<br>    &#39;word-viewer&#39;: WordView;<br>  }<br>}</pre><p>The above example is for showing words one after another in the browser. I explained the details of <a href="https://edisondevadoss.medium.com/web-components-introduction-5007ee4908e6">the code here</a> if you need to read it.</p><p>Now, create a file name word-viewer.stories.ts under the stories folder.</p><p>Import the below required plugin into the file.</p><pre>import type { Meta, StoryObj } from &#39;@storybook/web-components-vite&#39;;<br>import {WordView} from &#39;../components/word-viewer&#39;;</pre><p>Create an meta object which is for the UI documentation details — We configure it.</p><pre>const meta = {<br>  title: &#39;Example/WordViewer&#39;,<br>  tags: [&#39;autodocs&#39;],<br>  component: &#39;word-viewer&#39;<br>} satisfies Meta&lt;WordView&gt;;<br><br>export default meta;</pre><p>If you want to know what Typescript <strong>satisfies is r</strong><a href="https://www.freecodecamp.org/news/typescript-satisfies-operator/"><strong>ead it here</strong></a><strong>.</strong></p><p>The <strong>meta</strong> object should be the default export of our story file.</p><pre>type Story = StoryObj&lt;WordView&gt;;</pre><p>The above line creates a story object that contains details on how to render a story.</p><pre>export const Default: Story = {<br>  args: {<br>    words: &#39;Hello, World!&#39;<br>  }<br>};<br><br>export const Custom: Story = {<br>    args: {<br>      words: &#39;I am a custom word viewer!&#39;<br>    }<br>  };</pre><p>The above code has two stories, Default and Custom — Each one has its own values for props. The above one is minimal, we can do plenty of things with a Story.</p><h4>Output:</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-sdYmv_KHyPahk1sfxE5JA.png" /></figure><p>Now, you can see the storybook output for the worldview component. You can see the Default and Custom stories with their arguments.</p><p>Thank you for reading. Have a nice day!</p><h4>Reference link:</h4><p>Source code: <a href="https://github.com/EdisonDevadoss/lit-webcomponent-tutorial/tree/configure-storybook">https://github.com/EdisonDevadoss/lit-webcomponent-tutorial/tree/configure-storybook</a></p><ul><li><a href="https://www.freecodecamp.org/news/typescript-satisfies-operator/">How to Use the TypeScript satisfies Operator</a></li><li><a href="https://storybook.js.org/docs/writing-stories">How to write stories | Storybook docs</a></li><li><a href="https://storybook.js.org/docs/writing-stories/typescript">Writing stories in TypeScript | Storybook docs</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8644e9d13d42" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Web Components Introduction]]></title>
            <link>https://edisondevadoss.medium.com/web-components-introduction-5007ee4908e6?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/5007ee4908e6</guid>
            <category><![CDATA[html]]></category>
            <category><![CDATA[resuseable-ui]]></category>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[lit]]></category>
            <category><![CDATA[custom-component]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Tue, 10 Jun 2025 04:52:56 GMT</pubDate>
            <atom:updated>2025-06-10T04:52:56.087Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi Friends, In this post, I would like to share my insight about web components and how they work and how we build one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bABenSPs6m6C17_6MEDvtg.png" /><figcaption><a href="https://www.photopea.com/">https://www.photopea.com/</a></figcaption></figure><h4>What is a web component?</h4><p>It is creating new reusable and encapsulated HTML tags using the custom elements API in HTML. The newly created tags have their functionality and behaviour.</p><p>We can relate this to Bootstrap or Material UI library — We can access reusable buttons, input boxes, etc..</p><p>Once we build our web components, we can publish them in the npm registry. Then, we can use it in all the projects.</p><h4>Web component development</h4><p>There are two ways we can create a web component,</p><ol><li>create it from <strong>HTMLElement</strong> — Vanilla approach</li><li>create it from a third-party library like <strong>lit, stenciljs</strong></li></ol><h4>Create it from HTMLElement</h4><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>  &lt;head&gt;<br>    &lt;meta charset=&quot;UTF-8&quot; /&gt;<br>    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;<br>    &lt;title&gt;Document&lt;/title&gt;<br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;style&gt;<br>      lut-button {<br>        background-color: red;<br>        padding: 20px;<br>      }<br>    &lt;/style&gt;<br>    &lt;lut-button&gt;Hello&lt;/lut-button&gt;<br>    &lt;script&gt;<br>      class LutButton extends HTMLElement {<br>        constructor() {<br>          super();<br><br>          this.addEventListener(&#39;click&#39;, () =&gt; {<br>            console.log(&#39;cliked&#39;);<br>          });<br>        }<br>      }<br><br>      customElements.define(&#39;lut-button&#39;, LutButton);<br>    &lt;/script&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><p>If you look at the above code, I have created a new tag <strong>&lt;lut-button&gt;</strong> using the JavaScript class method. Here I have defined a class LutButton that extends from <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement">HTMLElement</a>, and I have added an event listener inside the constructor, which listens whenever <strong>LutButton</strong> is clicked.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lMHUgyDOk3Vd1MftE76c0Q.png" /></figure><p>You can see the output in the above screenshot.</p><p>The above one is just a Hello World of web component creation using HTMLElement. HTMLElement has more features. An important one is the Lifecycle callback.</p><p><strong>Lifecycle callbacks:</strong></p><p>There are three lifecycle callbacks we should know.</p><ol><li><strong>connectedCallback</strong> — called each time the element is added to the document.</li><li><strong>disconnectedCallback</strong> — called each time the element is removed from the document</li><li><strong>attributeChangedCallback</strong> — Called when attributes are changed, added, removed, or replaced.</li></ol><p>Let&#39;s move to another way of building web components — using a third-party library.</p><h4>Web component using Lit:</h4><pre>$ npm create vite@latest</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dKxtyMXGjgjmyyyMOq_XTA.png" /></figure><p>In the above image, you can see the option selected. We have to choose Lit as a selected framework.</p><pre>$ cd web-component<br>$ code .</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LXAo7Vbs7Me5CII3fMZbBg.png" /></figure><p>You can see the above folders and files of the project. Application starts from the index.html file, where we imported <strong>&lt;my-element&gt;</strong>.</p><p>Now we can create a new Element — create a new file named <strong>word-viewer.ts </strong>under the src folder.</p><pre>import { LitElement, html, css } from &#39;lit&#39;;<br>import { customElement, property, state } from &#39;lit/decorators.js&#39;;<br>import { classMap } from &#39;lit/directives/class-map.js&#39;;<br><br>@customElement(&#39;word-viewer&#39;)<br>export class WordView extends LitElement {<br>  static styles = css`<br>    :host {<br>      color: violet;<br>      cursor: pointer;<br>      padding 2rem;<br>      display: block;<br>    }<br><br>    pre{<br>     background: white;<br>     padding: 0.2rem;<br>    }<br>  `;<br><br>  @state() private idx = 0;<br><br>  @state() private playDirection = 1;<br><br>  @property() words: string = &#39;&#39;;<br><br>  private intervalTimer?: number;<br><br>  override connectedCallback() {<br>    super.connectedCallback();<br>    this.intervalTimer = setInterval(() =&gt; {<br>      this.tickToNextWord();<br>    }, 1000);<br>  }<br><br>  override disconnectedCallback() {<br>    super.disconnectedCallback();<br>    clearInterval(this.intervalTimer);<br>    this.intervalTimer = undefined;<br>  }<br><br>  render() {<br>    const splitedWords = this.words.split(&#39; &#39;);<br>    return html`&lt;pre<br>      @click=${this.switchPlayDirection}<br>      class=&quot;${classMap({ backwards: this.playDirection === -1 })}&quot;<br>    &gt;<br>    ${splitedWords[<br>        ((this.idx % splitedWords.length) + splitedWords.length) %<br>          splitedWords.length<br>      ]}<br>    &lt;/pre<br>    &gt;`;<br>  }<br><br>  tickToNextWord() {<br>    this.idx += this.playDirection;<br>  }<br><br>  switchPlayDirection() {<br>    this.playDirection *= -1;<br>  }<br>}<br><br>declare global {<br>  interface HTMLElementTagNameMap {<br>    &#39;word-viewer&#39;: WordView;<br>  }<br>}</pre><p>Look at the above example, <strong>WordView</strong> class extends from <strong>LitElement</strong>. LitElement is a wrapper of the <strong>HTMLElement</strong>.</p><p>We can access the WordView element in the HTML file.</p><pre>&lt;!doctype html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>  &lt;head&gt;<br>    &lt;meta charset=&quot;UTF-8&quot; /&gt;<br>    &lt;link rel=&quot;icon&quot; type=&quot;image/svg+xml&quot; href=&quot;/vite.svg&quot; /&gt;<br>    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;<br>    &lt;title&gt;Vite + Lit + TS&lt;/title&gt;<br><br>    &lt;script type=&quot;module&quot; src=&quot;/src/word-viewer.ts&quot;&gt;&lt;/script&gt; <br>  &lt;/head&gt;<br>  &lt;body&gt;<br><br>    &lt;word-viewer words=&quot;I am Edison Devadoss&quot;&gt;&lt;/word-viewer&gt;<br><br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><p>Using @customElement(‘word-viewer’) , We can define a tag name.</p><p>Using @property() words: string = ‘’; We can dynamically pass a value from outside the component. Trigger components when updated.</p><p>Using @state() private idx = 0; We can update the state internally. It can not be accessed outside the component.</p><pre>private intervalTimer?: number;<br><br> override connectedCallback() {<br>    super.connectedCallback();<br>    this.intervalTimer = setInterval(() =&gt; {<br>      this.tickToNextWord();<br>    }, 1000);<br>  }<br><br>  override disconnectedCallback() {<br>    super.disconnectedCallback();<br>    clearInterval(this.intervalTimer);<br>    this.intervalTimer = undefined;<br>  }</pre><p>Look at the above code — These are lifecycle callbacks, connectedCallback is called when an element is added, and disconnectedCallback is called when the component is removed. Using these lifecycle callbacks, we initiated a time interval.</p><p>The above example is for showing words one after another.</p><h4>Run the above code</h4><pre>$ npm run dev</pre><p>Now we see the output in the browser using accessing <a href="http://localhost:5173/">http://localhost:5173/</a>.</p><h4>Publish library in the npm registry.</h4><p>To publish our web component to npm, we need to create a build first. Before that, we need to define the npm version in the package.json file.</p><pre>{<br>  &quot;name&quot;: &quot;word-viewer&quot;,<br>  &quot;private&quot;: false,<br>  &quot;version&quot;: &quot;0.1.4&quot;,<br>  &quot;main&quot;: &quot;dist/word-viewer.es.js&quot;,<br>  &quot;exports&quot;: {<br>    &quot;.&quot;: &quot;./dist/word-viewer.es.js&quot;<br>  },<br>  &quot;types&quot;: &quot;dist/word-viewer.d.ts&quot;,<br>  &quot;files&quot;: [<br>    &quot;dist&quot;,<br>    &quot;types&quot;<br>  ],<br>  &quot;scripts&quot;: {<br>    &quot;dev&quot;: &quot;vite&quot;,<br>    &quot;build&quot;: &quot;tsc &amp;&amp; vite build&quot;,<br>    &quot;preview&quot;: &quot;vite preview&quot;<br>  },<br>  &quot;dependencies&quot;: {<br>    &quot;lit&quot;: &quot;^3.3.0&quot;<br>  },<br>  &quot;devDependencies&quot;: {<br>    &quot;typescript&quot;: &quot;~5.8.3&quot;,<br>    &quot;vite&quot;: &quot;^6.3.5&quot;,<br>    &quot;vite-plugin-dts&quot;: &quot;^4.5.4&quot;<br>  }<br>}</pre><p>The above code is my package.json file. Here I change the given version, we can start from 0.1.0.</p><p>“main”: “dist/word-viewer.es.js” — Where we start our code execution.</p><pre>&quot;exports&quot;: {<br>    &quot;.&quot;: &quot;./dist/word-viewer.es.js&quot;<br>  },</pre><p>The above one is what we are exporting code, it tells which part of the code is publicly available.</p><p>“types”: “dist/word-viewer.d.ts” — It has a type definition</p><p>“files”: [ “dist”] — It has what is published when it pushes it to the npm registry.</p><p>We have to add a README.md file where we have to mention the installation steps for our package.</p><p>Now, we can build our application.</p><pre>$ npm run build</pre><p>Now you see a folder name dist where you can find files.</p><h4>Npm Signup</h4><p>Create an account in npm by <a href="https://www.npmjs.com/signup">clicking here</a></p><p>After the signup is completed, we log into npm in our terminal.</p><pre>$ npm login</pre><p>The above command will open a login link in the browser where we need to give our username and password.</p><p>Once npm is published, run the following command. It takes a few seconds to publish our npm.</p><pre>$ npm publish</pre><p>Once the above command is executed, we can see the package in the npm registry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_Sx0xVW_OJoS7IoJArI_Xg.png" /></figure><p>You can see my published package in the link below.</p><p><a href="https://www.npmjs.com/package/word-viewer">word-viewer</a></p><p>Now we can use this package everywhere. I have mentioned how to use it in a React application in this package.</p><p>Thank you for reading. Have a nice day!</p><h4>Reference links:</h4><p>Code (Create Web component using HTMLElement): <a href="https://github.com/EdisonDevadoss/webcomponent-project">https://github.com/EdisonDevadoss/webcomponent-project</a></p><p><a href="https://github.com/EdisonDevadoss/lit-webcomponent-tutorial">https://github.com/EdisonDevadoss/lit-webcomponent-tutorial</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5007ee4908e6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Book a Google calendar by using Gemini LLM]]></title>
            <link>https://medium.com/yavar/book-a-google-calendar-by-using-gemini-llm-e841f2f7607f?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/e841f2f7607f</guid>
            <category><![CDATA[google-api]]></category>
            <category><![CDATA[googlecalendarbookingai]]></category>
            <category><![CDATA[calendarbookingbygemini]]></category>
            <category><![CDATA[gemini]]></category>
            <category><![CDATA[geiminicalendar]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Tue, 13 May 2025 11:05:23 GMT</pubDate>
            <atom:updated>2025-05-14T07:54:06.850Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi friends, in this post, I will explain how to build a node application which connects with Gemini LLM and, using a prompt, we book a Google Calendar event.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3nRo6Iw9E9_1dQalASKzuw.png" /><figcaption><a href="https://www.photopea.com/">https://www.photopea.com</a></figcaption></figure><h4>Set up an account in Google APIS.</h4><p>Click <a href="https://console.cloud.google.com/apis">the link</a> and set up a Google account, then access the API services.</p><p>Then search in the service bar “calendar” and enable the calendar API. Consider the image below as a reference.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M19TB4so-_IlVYwReXvsgg.png" /><figcaption>Enable Google Calendar</figcaption></figure><p>After the calendar is enabled, configure the authorised redirect URI for authorisation.</p><p>Once authorised with our Google account, it will redirect to the configured URL and give an access code. We need this access code for Google Calendar booking.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ap9t4DlHBrb-bic8ywZl_Q.png" /><figcaption>authorization URIs configuration</figcaption></figure><h4>Define the Auth callback</h4><pre>import Fastify from &#39;fastify&#39;;<br><br>const fastify = Fastify({<br>  logger: true<br>});<br><br>// Declare a route<br>fastify.get(&#39;/auth&#39;, async function handler(request, reply) {<br>  console.log(&#39;request&#39;, request);<br>  return { hello: request.query };<br>});<br><br>try {<br>  await fastify.listen({ port: 3000 });<br>} catch (err) {<br>  fastify.log.error(err);<br>  process.exit(1);<br>}</pre><p>The above code is just a Fastify application setup and configured with one route with the GET method. In request.query We can find the access code.</p><pre>$ node server.js</pre><p>Now we can run the application using the above command.</p><h4><strong>Generate Gemini API</strong></h4><p>Once the Google API is configured now we can generate the Gemini API key and configuration.</p><p>Click this <a href="https://aistudio.google.com/apikey">link</a> to generate an API key. After generating of API key, configure it in the .env file.</p><pre>GEMINI_API_KEY=</pre><p>Not only the Gemini API key, but we can also configure the following in .env</p><pre>GOOGLE_CLIENT_ID=&#39;&#39;<br>GOOGLE_CLIENT_SECRET=&#39;&#39;<br>REDIRECT_URI=&#39;&#39;</pre><p>Install the required library in our application.</p><pre>$ npm install @google/genai dotenv fastify googleapis --save</pre><p>Run the above command to install all required libraries.</p><p>Create aindex.js file where we can write our code for calendar booking.</p><pre>import { configDotenv } from &#39;dotenv&#39;;<br>configDotenv();<br><br>import { GoogleGenAI, FunctionCallingConfigMode } from &#39;@google/genai&#39;;<br>import { google } from &#39;googleapis&#39;;<br>import * as readline from &#39;readline&#39;;<br><br>const { GEMINI_API_KEY, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, REDIRECT_URI } =<br>  process.env;<br><br>const rl = readline.createInterface({<br>  input: process.stdin,<br>  output: process.stdout<br>});<br><br>const genAI = new GoogleGenAI({ apiKey: GEMINI_API_KEY });<br>const geminiModel = genAI.models;<br><br>const oauth2Client = new google.auth.OAuth2(<br>  GOOGLE_CLIENT_ID,<br>  GOOGLE_CLIENT_SECRET,<br>  REDIRECT_URI<br>);</pre><p>In the above, we import the library needed for the application and initialise the GoogleGenAI model and the oauthClient library.</p><p>We have used it readline for interaction with the node application via the terminal.</p><pre><br>async function authenticate() {<br>  const authUrl = oauth2Client.generateAuthUrl({<br>    access_type: &#39;offline&#39;,<br>    scope: &#39;https://www.googleapis.com/auth/calendar.events&#39;<br>  });<br>  console.log(&#39;Authorize this app by visiting this url:&#39;, authUrl);<br>  rl.question(&#39;Enter the code from that page here: &#39;, async (code) =&gt; {<br>    try {<br>      const tokenResponse = await oauth2Client.getToken(code);<br>      tokens = tokenResponse.tokens;<br>      oauth2Client.setCredentials(tokens);<br>      calendar = google.calendar({ version: &#39;v3&#39;, auth: oauth2Client });<br>      console.log(&#39;Authentication successful!&#39;);<br>      queryGemini();<br>    } catch (err) {<br>      console.error(&#39;Error retrieving access token&#39;, err);<br>      rl.close();<br>    }<br>  });<br>}</pre><p>The above function is for authentication with the Google API for calendar access.</p><p>rl.question It asks for our input via the terminal. Once we are authorised using authUrl, we can enter the code in our terminal or paste it.</p><p>This function generates a token for our further access. Then it will call queryGemini() function.</p><pre>const chatHistory = [];<br><br>let isBooked = false;<br><br><br>async function queryGemini() {<br>  rl.question(&#39;Ask Gemini (or say &quot;book calendar&quot;): &#39;, async (query) =&gt; {<br>    try {<br>      chatHistory.push({<br>        role: &#39;user&#39;,<br>        parts: [{ text: query }]<br>      });<br><br>      const result = await geminiModel.generateContent({<br>        model: &#39;gemini-2.0-flash-001&#39;,<br>        contents: chatHistory,<br>        config: {<br>          systemInstruction: `<br>You are a smart assistant that helps users with any kind of query.<br><br>If the user explicitly says they want to book a calendar, schedule a meeting, or talk to an agent, interpret it as a request to create a calendar event and use the \`create_calendar_event\` function (if enough information is available). Ask only the necessary missing details.<br><br>Once the calendar event is successfully created, return to normal assistant behavior. Do not continue suggesting or prompting about calendar bookings unless the user explicitly brings it up again.<br><br>For all other types of queries, respond normally as a helpful assistant.<br>`,<br>          toolConfig: {<br>            functionCallingConfig: {<br>              mode: FunctionCallingConfigMode.AUTO<br>            }<br>          },<br>          tools: tools<br>        }<br>      });<br><br>      const response = result;<br><br>      console.log(&#39;Gemini:&#39;, response.text);<br><br>      if (!isBooked &amp;&amp; response.functionCalls?.length &gt; 0) {<br>        const func = response.functionCalls[0];<br>        console.log(&#39;Function call received:&#39;);<br>        console.log(&#39;Function name:&#39;, func.name);<br>        console.log(&#39;Arguments:&#39;, func.args);<br>        const args = func.args;<br><br>        if (func.name === &#39;create_calendar_event&#39;) {<br>          await bookAEvent(<br>            args.title,<br>            args.date,<br>            args.time,<br>            args.email,<br>            args.timezone<br>          );<br>          isBooked = true;<br>        }<br>      } else if (response.text) {<br>        chatHistory.push({<br>          role: &#39;model&#39;,<br>          parts: [{ text: result.text }]<br>        });<br>      }<br>    } catch (error) {<br>      console.error(&#39;Error querying Gemini:&#39;, error);<br>    }<br>    queryGemini(); // Continue the REPL<br>  });<br>}</pre><p>In the above code, using geminiModel.generateContent() We can interact with our Gemini LLM.</p><p>There are a few more things we configured with the generateContent method.</p><ol><li>model — AI model name with version</li><li>contents — we are passing chat history — both user input and model response. We store those values chatHistory array.</li><li>config — In the config, we set up system instruction, toolConfig, and tools.</li><li>systemInstruction — It is a prompt message on how our system should act.</li></ol><p>If you look at the system instructions, we mentioned the call create_calander_event function if the user requests a book an event or talk to an agent.</p><pre>     toolConfig: {<br>            functionCallingConfig: {<br>              mode: FunctionCallingConfigMode.AUTO<br>            }</pre><p>The above configuration defines the mode of function call, it can be <strong>AUTO</strong> or <strong>ANY</strong>.</p><p>If we set ANY it will call the time user chats, it AUTO means once matched with the system instruction.</p><h4>Define the create calendar event function</h4><pre>onst tools = [<br>  {<br>    functionDeclarations: [<br>      {<br>        name: &#39;create_calendar_event&#39;,<br>        description: &#39;Create a calendar event based on user input.&#39;,<br>        parameters: {<br>          type: &#39;object&#39;,<br>          properties: {<br>            title: {<br>              type: &#39;string&#39;,<br>              description: &#39;The title of the event&#39;<br>            },<br>            email: {<br>              type: &#39;string&#39;,<br>              description: &quot;User&#39;s email of the event (e.g., edison@yavar.ai)&quot;<br>            },<br>            date: {<br>              type: &#39;string&#39;,<br>              description: &#39;The date of the event (e.g., 2025-05-10)&#39;<br>            },<br>            time: {<br>              type: &#39;string&#39;,<br>              description: &#39;The time of the event (e.g., 3:00 PM)&#39;<br>            },<br>            timezone: {<br>              type: &#39;string&#39;,<br>              description:<br>                &#39;The timezone of the event (e.g., Asia/Kolkata, America/New_York)&#39;<br>            }<br>          },<br>          required: [&#39;title&#39;, &#39;email&#39;, &#39;date&#39;, &#39;time&#39;, &#39;timezone&#39;]<br>        }<br>      }<br>    ]<br>  }<br>];</pre><p>Using the above code, we defined functionDeclarations and set up a function named create_calendar_event and defined what the inputs are that we need to collect from the user for calendar booking.</p><p>Once the user enters the required fields for calendar booking, we can find values in response.functionCalls.</p><pre> if (response.functionCalls?.length &gt; 0) {<br>      const func = response.functionCalls[0];<br>   console.log(&#39;func is&#39;, func);<br>   console.log(&#39;Function name:&#39;, func.name);<br>   console.log(&#39;Arguments:&#39;, func.args);<br>    const args = func.args;<br><br>  <br>   if (func.name === &#39;create_calendar_event&#39;) {<br>          await bookAEvent(<br>            args.title,<br>            args.date,<br>            args.time,<br>            args.email,<br>            args.timezone<br>          );<br>          isBooked = true;<br>        }<br>      } <br>}</pre><p>Now you can find values in the logs and call bookAEvent function.</p><pre>async function bookAEvent(title, date, time, mail, timezone) {<br>  const startDateTime = new Date(`${date} ${time}`);<br>  const endDateTime = new Date(startDateTime.getTime() + 60 * 60 * 1000); // +1 hour<br><br>  const event = {<br>    summary: title, <br>    start: {<br>      dateTime: startDateTime.toISOString(), <br>      timeZone: timezone<br>    },<br>    end: {<br>      dateTime: endDateTime.toISOString(),<br>      timeZone: timezone<br>    },<br>    attendees: [<br>      { email: &#39;edisonj1996@gmail.com&#39; },<br>      { email: mail }<br>      // Replace with extracted attendees<br>    ],<br>    conferenceData: {<br>      createRequest: {<br>        requestId: &#39;meet-&#39; + Date.now(),<br>        conferenceSolutionKey: {<br>          type: &#39;hangoutsMeet&#39;<br>        }<br>      }<br>    }<br>  };<br>  console.log(&#39;evet is&#39;, event);<br><br>  if (calendar) {<br>    const calendarResponse = await calendar.events.insert({<br>      calendarId: &#39;primary&#39;, // Use &#39;primary&#39; for the user&#39;s main calendar<br>      auth: oauth2Client,      resource: event,<br>      conferenceDataVersion: 1<br>    });<br>    // console.log(&#39;bookAEvent is&#39;, calendarResponse);<br>  }<br>}</pre><p>The above function is for creating an event in the calendar. We need to pass the following details in the resource.</p><ol><li>summary — event tittle</li><li>start — start time with timezone</li><li>end—end time with timezone</li><li>attendees — email of all attendees.</li><li>confererceData — it is for generating a g-meet link</li></ol><p>Now, calling node index.jsWe can run the application and chat with Gemini, and book an event in Google Calendar.</p><h4>References:</h4><p>Access full code — <a href="https://github.com/EdisonDevadoss/gemini-calendar-booking-node-example/tree/main">https://github.com/EdisonDevadoss/gemini-calendar-booking-node-example/tree/main</a></p><p>Google APIs — <a href="https://console.cloud.google.com/apis">https://console.cloud.google.com/apis</a></p><p>Gemini API key — <a href="https://aistudio.google.com/apikey">https://aistudio.google.com/apikey</a></p><p>Thank you for reading. Have a nice day!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e841f2f7607f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/yavar/book-a-google-calendar-by-using-gemini-llm-e841f2f7607f">Book a Google calendar by using Gemini LLM</a> was originally published in <a href="https://medium.com/yavar">YavarTechWorks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Understanding of Docker]]></title>
            <link>https://medium.com/yavar/my-understanding-of-docker-d18e758537b0?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/d18e758537b0</guid>
            <category><![CDATA[docker-basic]]></category>
            <category><![CDATA[whyshouldknowdocker]]></category>
            <category><![CDATA[docker-image]]></category>
            <category><![CDATA[docker-terminologies]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Fri, 28 Mar 2025 10:03:49 GMT</pubDate>
            <atom:updated>2025-03-28T10:03:49.360Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi friends, I would like to share my understanding of docker and why we should know about it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YEKH0Px2vIkZbSi97HxFeQ.png" /><figcaption><a href="https://www.photopea.com/">https://www.photopea.com</a></figcaption></figure><h4>What is docker</h4><p>The official document explanation is as follows:</p><blockquote>Accelerate how you build, share, and run applications. Docker helps developers build, share, run, and verify applications anywhere — without tedious environment configuration or management.</blockquote><p>I will define what strikes in my heart after learning and working with docker.</p><p><em>It makes our application platform agnostic. Our application is no longer embedded with the platform(run-time), configurations and other dependencies. It gives us a lot of flexibility when running our application.</em></p><p><em>By using docker, we can package our app and running environment as a single container, and that container can also be distributed.</em></p><h4>What problem does docker solve?</h4><p>Docker gives one isolated environment to run the app.</p><p>It is easy to run different versions of services on the same machine.</p><p>We should be familiar with a few important docker concepts. I will explain them as follows:</p><h4>Docker Image</h4><p>It is a standalone executable of the software that includes everything needed to run the application (code, configuration, Run time, etc)</p><h4>Container:</h4><p>It is running an instance of a docker image. The Docker container runs in the Docker engine.</p><h4>Dockerfile:</h4><p>If we want to dockerize our application, we need a Dockerfile in our project — it should have instructions to build docker images.</p><h4>Repository:</h4><p>Collection of container images or versions of an application stored within a registry. Registry has multiple repositories — Docker hub is the public registry.</p><h4>Run a docker service:</h4><p>To run a docker service, we should be familiar with a few comments.</p><p>Check existing running docker container:</p><pre>$ docker ps</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Sxk855w_yo9jWb6jGbTS-Q.png" /></figure><p>docker ps command gives details of running containers on my machine.</p><h4>How to run a new service:</h4><p>There are plenty of open-source services available in the <a href="https://hub.docker.com/">Docker hub</a> where we can find a service and run it on our machine.</p><p>Now, I will run an Nginx service on my machine using docker. You can find the Nginx docker service <a href="https://hub.docker.com/_/nginx">here</a>.</p><p>First, we have to pull the Nginx service image in our local machine. To do this, we have to use the below command.</p><pre>$ docker pull nginx:latest</pre><p>Using the tag <strong>:latest</strong> we can pull the latest version of the service. Instead of this we can give our perferfable version also.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Okhwcixlk3_QZho5158hoQ.png" /></figure><p>The above command will download a new nginx service image.</p><pre>$ docker images</pre><p>The above command is for checking existing images.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qmskRhXyR2pNuwBzrd4FZA.png" /></figure><p>In the above image, we can newly download the nginx docker image.</p><h4>Run a docker image</h4><p>To run a docker image, we have to use the below command</p><pre>$ docker run ngnix</pre><p>In the above command, we have to give the docker image name. In my case, it is Nginx.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bIQkkAf-2x1WaeLen0Zilw.png" /></figure><p>You can see docker running logs.</p><p>Now, we can access the newly created docker image instance(Container).</p><p>To run <a href="http://localhost/">http://localhost:80</a> in the browser, we can access the application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OvqQzdfRRbdwjZheJQ4MLQ.png" /><figcaption><a href="http://localhost/">http://localhost:80</a></figcaption></figure><p>But, by clicking <strong>ctrl+c</strong> we can close the application, after that we can not access the service in the browser.</p><p>To fix this, we have run the service in the <strong><em>detach</em></strong> mode.</p><pre>$ docker run -d nginx</pre><p>By using -d in the option, we can run the application in the background mode.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GITa8eU5xelSGatSaE47AQ.png" /></figure><p>Now, we can find the <strong>nginx</strong> container in the list of running containers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9cgBUHZpzj0tFWwRG3swRg.png" /></figure><p>In the above image, you can see Nginx as an actively running container.</p><h4>Stop a Container</h4><p>To stop a container, we have to give the command<em> docker stop &lt;Container ID&gt;.</em></p><pre>$ docker stop 0b652d49e264</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Nva0WleWM4kVYth-Cfxt6w.png" /></figure><p>After the container is stopped, it will not be present when we run the <strong><em>docker ps</em></strong> command.</p><p>If we want to see all the containers, including stopped containers, we can give the below command.</p><pre>$ docker ps -a</pre><p>At this juncture, I conclude this post. I hope I will write more posts about docker.</p><p>Not but not least, Docker is a vast subject to learn, and its use cases also border. To know further refer <a href="https://docs.docker.com/get-started/">docker official document</a>.</p><p>Thank you for reading. Have a nice day!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d18e758537b0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/yavar/my-understanding-of-docker-d18e758537b0">My Understanding of Docker</a> was originally published in <a href="https://medium.com/yavar">YavarTechWorks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Handle Multiple git accounts in a system]]></title>
            <link>https://medium.com/yavar/handle-multiple-git-accounts-in-a-system-8f9a57cceec5?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/8f9a57cceec5</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[multiple-github-accounts]]></category>
            <category><![CDATA[git]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Sat, 01 Feb 2025 04:03:44 GMT</pubDate>
            <atom:updated>2025-02-01T04:03:44.629Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*60hsKE28pn7llhdAPsZj_g.png" /><figcaption><a href="https://www.photopea.com/">https://www.photopea.com</a></figcaption></figure><p>Hi friends, in this post I will share how to use multiple git accounts on our machine.</p><p>I hope, most of the developers are like me to have their own GitHub account and their company&#39;s git account.</p><p>By default, we can configure our system or laptop with only one git account. So whenever we are working with repositories under that git account we do not need to give a <strong>username</strong> and <strong>password</strong> Token for each git operation — git pull, push, fetch.</p><p>But sometimes we may need to work on repositories which are in other git providers such gitlab or Azure Git. In that case, we need to give a git <strong>username</strong> and <strong>password</strong> for every git operation (Communicating with git cloud).</p><p>To avoid giving <strong>username</strong> and <strong>password</strong> tokens for every git operation, we can configure username and password tokens with those repositories.</p><h4>Git Configuration with the project Repo</h4><p>Here, I have a GitHub repository that is connected to my personal GitHub account, but my system does not configure my personal GitHub account. So I can not use the <strong>SSH</strong> method to add a remote URL in the the project folder. So I need to use the <strong>HTTPS</strong> method to add a remote URL in the project folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1016/1*NPqDKOIaeEk-63mZAkDG-Q.png" /></figure><p>If I want to push my code, it will ask <strong>username</strong> and <strong>password</strong>. You can see this in the above image.</p><p>By entering my <strong>username</strong> and <strong>password</strong>, I can push my code. Here, I need to do this for every <strong>push</strong>, <strong>pull</strong>, and <strong>fetch</strong> operation. Is it not redundant work?</p><h4>How do we avoid the repetition?</h4><ol><li>Open the git config file of the project</li></ol><pre>vim .git/cofig</pre><p>It will open the following file. Here I added a Username and Password.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0zGmMqxxYDVqmb-QtsPSEA.png" /></figure><p>2. Here we need to add Username and Password.</p><pre>    url = https://github.com/EdisonDevadoss/javascript-problem-solving.git</pre><p>By default it shows like this above, we need to add &lt;<strong>Username</strong>&gt;: and &lt;<strong>Password</strong>&gt;</p><p>Then save the file. Now run the <strong>git push </strong>command.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gotJZcHeXD9lTfEPiyIdsA.png" /></figure><p>Now It will not ask us to enter a <strong>username</strong> and <strong>password</strong> for any git operation.</p><p>I hope this tip is helpful.</p><p>Thank you for reading. Have a nice day!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f9a57cceec5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/yavar/handle-multiple-git-accounts-in-a-system-8f9a57cceec5">Handle Multiple git accounts in a system</a> was originally published in <a href="https://medium.com/yavar">YavarTechWorks</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Review — Same as Ever Book]]></title>
            <link>https://edisondevadoss.medium.com/review-same-as-ever-book-ede4de84493a?source=rss-5081eccbbef1------2</link>
            <guid isPermaLink="false">https://medium.com/p/ede4de84493a</guid>
            <category><![CDATA[psychology-of-money]]></category>
            <category><![CDATA[same-as-ever]]></category>
            <category><![CDATA[morgan-housel]]></category>
            <category><![CDATA[book-review]]></category>
            <category><![CDATA[history]]></category>
            <dc:creator><![CDATA[Edison Devadoss]]></dc:creator>
            <pubDate>Tue, 26 Nov 2024 06:33:58 GMT</pubDate>
            <atom:updated>2024-11-26T06:33:58.081Z</atom:updated>
            <content:encoded><![CDATA[<h3>Review — Same as Ever Book</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/324/1*24hUtky3ESeZBJYyTFVUcA.jpeg" /><figcaption><a href="https://www.goodreads.com/book/show/125116554-same-as-ever">Good Reads</a></figcaption></figure><p>Hi friends, I would like to give my insights about the book Same as Ever by Morgen Housel.</p><p>We often hear the quote “Only things never change is everything changes” But there are certain things in the world that never be changed by the time. Examples: Human behaviour, greed, emotions, uncertainties in the world, etc.</p><p>Morgen Housel presented twenty-three Same as Ever chapters. I want to share a few of the concepts which inspired me.</p><h4>Hanging by a Thread</h4><p>If you know where we have been, you realize we have no idea where we are going.</p><p>Our lives and history are often shaped by random events which are completely out of our control.</p><p>During World War I, one of the American steamships was travelling from New York to Liverpool. The ship&#39;s captain shut down the fourth boiler to save money. But this decision would slow the ships’ voyage by one day.</p><p>Because of this delay, the American ship and German submarine sailed the same path and finally, the German submarine hit the American ship and twelve hundred passengers were killed.</p><p>This incident was the most important trigger for getting the public opinion of America to enter World War I.</p><p>If the fourth boiler had not shut down, the American ship would have been reached one day before it met the German submarine and they could have avoided World War I.</p><p>You can play this game all day. Every big story could have turned out differently if a few little puffs of nothingness had gone the other direction.</p><p>Every current event — Big or Small — has parents, grandparents, great-grandparents, siblings and cousins. Ignoring that family tree can muddy our understanding of events, giving a false impression of why things happened, how long they might last, and under what circumstances they might occur again.</p><p>People like to say, To know where we are going, you have to know where we have been. But more realistic is admitting that if you know where we have been, you realize we have no idea where we are going.</p><p>The second un-changeable rule is, “Expectations and Reality”.</p><h4>Expectations and Reality</h4><p>Once ninety-eight-year-old Chalie Munger was asked, You seem extremely happy and content. What is your secret to living a happy life? Munger replied:- The first rule of a happy life is low expectations. If you have unrealistic expectations you are going to be miserable your whole life. You want to have reasonable expectations and take life’s results, good and bad as they happen with a certain amount of stoicism.</p><p>Your happiness depends on your expectations more than anything else. So in a world that tends to get better for most people most of the time, an important life skill is getting the goalpost to stop moving. It is also one the the hardest.</p><p>The quality of life goes up, and people’s expectations also rise. Because those improvements also benefit other people around us, whose circumstances we anchor to.</p><blockquote>Montesquieu wrote Two hundred and seventy-five years ago, “If you only wished to be happy, this could be easily accomplished; but we wish to be happier than other people and this is always difficult, for we believe others to be happier than they are”.</blockquote><p>The third un-changeable rule is “The Best story wins”.</p><h4>The Best story wins</h4><p>Martin Luther King Jr.’s famous speech at the Lincoln Memorial on August 28, 1968, did not go according to plan.</p><p>King’s advisor and speechwriter, Clarence Jones, drafted a full speech for King to deliver, and based on that he started to deliver his speech.</p><p>The first few minutes of the king’s speech followed the script. The video shows him constantly looking down at his notes, and reading the script.</p><p>Just then, around halfway through the speech, gospel singer Mahalia Jackson — who was standing to kings’ left, maybe ten feet away. She shouted out “Tell them about the dream, Martin! Tell them about the dream!”</p><p>Then Martin, put aside his notes, grabs the lectern and looks out on more than 250,000 people. He took a six-second pause and said:</p><p>I have a dream. It is a dream deeply rooted in the American dream.</p><p>I have a dream that one day this nation will rise up and live out the true meaning of its creed: “We hold these truths to be self-evident, that all men are created equal”.</p><p>I have a dream that my four little children will one day live in a nation where they will not be judged by the colour of their skin but by the content of their character.</p><p>I have a dream today.</p><p>The rest is history. That portion of the speech which is most celebrated around the world is not the speech that he planned to give. King did not prepare this speech.</p><p>But this was one of the best stories ever told, evoking emotion and connecting the dots in millions of people’s heads in a way that changed history.</p><p>Sapiens is a famous book, by the author Yuval Noah Harari. He became a bestselling contemporary author.</p><p>He once agreed I did zero new research. I just read the kind of common knowledge and just presenting it in a new way. Sapiens is famous because of its beautiful writing. The best story wins.</p><blockquote>Often, It is not what you say, but how you say it. That matters.</blockquote><p>Not only these three chapters, I like all the 23 chapters in the book. If you have the habit of underlining the book, you will find something on every page to underline.</p><p>This is the one of best books I read this year.</p><p>Thank you for reading. Have a nice day!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ede4de84493a" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>