<?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 Crimsonsyrus on Medium]]></title>
        <description><![CDATA[Stories by Crimsonsyrus on Medium]]></description>
        <link>https://medium.com/@crimsonsyrus000?source=rss-ce6ac9595b76------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*-hgAQw0cCGMUtE-x</url>
            <title>Stories by Crimsonsyrus on Medium</title>
            <link>https://medium.com/@crimsonsyrus000?source=rss-ce6ac9595b76------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 06:59:45 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@crimsonsyrus000/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[ML Preprocessing: Your Guide to Clean and Ready Data]]></title>
            <link>https://medium.com/@crimsonsyrus000/ml-preprocessing-your-guide-to-clean-and-ready-data-ad5959c8bc64?source=rss-ce6ac9595b76------2</link>
            <guid isPermaLink="false">https://medium.com/p/ad5959c8bc64</guid>
            <dc:creator><![CDATA[Crimsonsyrus]]></dc:creator>
            <pubDate>Sat, 06 Sep 2025 13:00:38 GMT</pubDate>
            <atom:updated>2025-09-08T12:07:25.878Z</atom:updated>
            <content:encoded><![CDATA[<p>In machine learning, the quality of your data often determines the quality of your model. Before you feed your dataset into any algorithm, it’s crucial to prepare it carefully. This article walks you through the essential steps of ML preprocessing, entirely conceptually, with minimal code.</p><h3>1. Load and understand the Dataset</h3><p>The first step in any machine learning project is to truly <strong>understand your data</strong>. This goes beyond just glancing at a table of numbers; it involves thinking critically about what each column represents and the story behind it.</p><p>Key points to keep in mind:</p><ol><li><strong>Semantic meaning of columns</strong></li></ol><ul><li>Ask yourself: <em>What does this column actually measure?</em></li><li>Some numeric-looking columns may be categorical in nature, and treating them incorrectly can mislead your model. Some examples are:<br>- ZIP Codes (numbers represent locations, not quantities)<br>- Phone numbers (unique identifiers, no arithmetic meaning)<br>- Customer ID/ Employee ID(identifiers, not continuous values)</li></ul><p><strong>2. Check the values and distributions</strong></p><ul><li>Look at ranges, unusual values, or anomalies.</li><li>Identify potential errors, inconsistencies, or placeholders (like zeros or blanks) that might need special handling.</li></ul><p><strong>3. Relationships between columns</strong></p><ul><li>Don’t assume a correlation shown in visualizations always indicates a meaningful relationship.</li><li>Consider domain knowledge: do these features logically influence each other or the target?</li></ul><p><strong>4. Data collection context</strong></p><ul><li>Understand how the data was gathered: surveys, sensors, logs, or manual entry.</li><li>Knowing this helps you spot potential biases or limitations in the dataset.</li></ul><p><strong>5. Identify redundancy or irrelevant columns</strong></p><ul><li>Some columns may be duplicates or proxies for others.</li><li>Removing irrelevant features reduces noise and can improve model performance.</li></ul><p><strong>6. Think critically about categorical labels</strong></p><ul><li>Are the categories meaningful and consistent?</li><li>Could some categories be combined, or do some represent too few samples to be useful?</li></ul><p>💡 <strong>Bottom line:</strong> Understanding your data is not just a mechanical inspection; it’s about <strong>thinking like a detective</strong>, combining domain knowledge with intuition, and questioning every column. This foundational step guides every subsequent preprocessing decision and ensures that your model learns meaningful patterns rather than spurious correlations.</p><h3>2. Check Data Types</h3><p>Not all data is created equal. Some columns may appear numeric at first glance but are actually stored as text or object types.</p><p>A classic example is the <em>TotalCharges</em> column in the <a href="https://www.kaggle.com/datasets/blastchar/telco-customer-churn"><strong>Telco Churn dataset</strong></a>.</p><p>While it represents numeric values, it is stored as an <em>object</em>, which allows empty strings to appear for certain rows. These empty or misformatted entries are clearly undesirable, as they can lead to errors or missing values when training a model.</p><p>You can see that I have handled this here by dropping the rows that contain empty strings for <em>TotalCharges</em>. I can do that here, as there are only a few rows with this discrepancy in the big dataset, which contains about 7043 rows in total.</p><pre># Check unique non-numeric entries<br>print(df[&quot;TotalCharges&quot;].loc[df[&quot;TotalCharges&quot;].str.strip() == &quot;&quot;].shape)<br># Outputs (11, )<br><br># Convert to numeric (force errors to NaN)<br>df[&quot;TotalCharges&quot;] = pd.to_numeric(df[&quot;TotalCharges&quot;], errors=&quot;coerce&quot;)<br><br># Now check missing values<br>print(df[&quot;TotalCharges&quot;].isnull().sum())<br># Outputs 11<br><br># Drop rows since &lt; 1%, a very minimal portion of the dataset is affected<br>df = df.dropna(subset=[&quot;TotalCharges&quot;])</pre><p>Understanding the true type of each feature is essential because it determines how you handle it during preprocessing. Numeric columns require proper conversion before scaling, while categorical or textual columns need encoding. Similarly, empty or invalid entries in numeric columns must be addressed through cleaning or imputation to prevent issues downstream.</p><p>The best practice is to carefully inspect column types and the unique values they contain. This early step ensures that each feature is interpreted correctly and prevents subtle issues from propagating into your model. By taking the time to understand the data types, you lay a strong foundation for all subsequent preprocessing steps.</p><h3>3. Handle Missing Values</h3><p>Missing data is one of the most common challenges in machine learning and, if left unaddressed, can lead to biased or inaccurate models. Understanding why data is missing is the first step in deciding how to handle it.</p><p>Some reasons that can lead to missing data are:</p><ul><li><em>Non-response by people. </em>People skip survey questions (e.g., income, age).</li><li><em>Data entry errors.</em> Typos, accidental blanks, or incomplete forms.</li><li><em>Sensor failures. </em>Device malfunctions lead to missing readings.</li><li><em>Software bugs. </em>Data not saved properly due to crashes or sync issues.</li><li><em>Optional fields.</em> Dataset design allows certain values to be left empty.</li><li><em>Sampling differences. </em>Some variables are only collected for a subset of subjects.</li></ul><p>Note that the reasons involve human, system, and data collection methods as factors.</p><p>Sometimes values are missing completely at random, other times there is a pattern related to the feature itself or to the target variable. Ignoring missing entries or treating them incorrectly can distort the relationships in your dataset and negatively impact model performance.</p><p>There are multiple strategies to deal with missing values, each suitable for different scenarios.</p><p>One simple approach is to <strong>drop rows or columns</strong> that have too many missing values. This is effective when the proportion of missing data is small or when an entire column is mostly empty, making it unreliable for modeling. However, dropping data is not always ideal, especially if the missing information is substantial or carries meaningful insights.</p><p>Note the figure below. It shows the shape of the default <strong>Titanic Dataset</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/95/1*BFdEG0teazVAdBmC7_uJkA.png" /><figcaption>Original Shape</figcaption></figure><p>Then we check the columns will missing/null values.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*fss_uq2m2kNoIDt8bYk3ZQ.png" /></figure><p>age and deck have many rows with empty/null values. So we drop them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/160/1*oMYA24EsWbUxaTWHEdgzzA.png" /><figcaption>Shape after dropping rows</figcaption></figure><p>The result: we lost ~80% of the data points. <br>It goes to show that we should not drop arbitrarily. In this case, we could have judged the deckcolumn’s relevance and dropped the column itself, or made some imputation (filled up with values) in the column.</p><p><strong>Imputation </strong>is<strong> </strong>a more nuanced approach, which involves filling in missing values based on some reasonable assumption.</p><p><strong>Simple imputation</strong> strategies use basic statistics like the mean, median, or mode of the feature. These are easy to implement and often work well for numeric features with a roughly symmetric distribution or categorical features with a dominant category.</p><p><strong>Group-based imputation</strong> considers patterns within subgroups of your data — for example, filling in missing values based on the average within a particular category or demographic. This method preserves relationships between features and can lead to more accurate imputation than global statistics.</p><p><strong>Target-based imputation</strong> is another sophisticated approach where missing values are predicted using other features, effectively treating imputation as a mini supervised learning problem. This can be powerful but requires caution, as it can unintentionally introduce <strong>data leakage</strong> if the target variable from the test set is used during imputation.</p><p>Another valuable technique related to handling numeric features is <strong>binning</strong>, which involves grouping continuous values into discrete intervals or categories. Binning can simplify complex distributions, reduce the effect of outliers, and sometimes improve model interpretability. For example, ages could be binned into ranges like 0–18, 19–35, 36–50, and 51+, or income could be grouped into quartiles. Binning is particularly useful when the precise numeric value is less important than the range or category to which it belongs.</p><p>Ultimately, the goal is to handle missing data thoughtfully and deliberately. Each strategy — whether dropping, simple imputation, group-based imputation, target-based imputation, or binning — comes with trade-offs. The key is to ensure that the chosen method preserves meaningful patterns in the data without introducing bias or leaking information from the test set. Proper handling of missing values and thoughtful binning lay a strong foundation for all subsequent preprocessing and modeling steps.</p><h3>4. Prevent Data Leakage</h3><p>Data leakage is a subtle but serious issue in machine learning that can drastically undermine the reliability of your models. In simple terms, data leakage occurs when information from outside the training set inadvertently influences the model during training. This means that the model ends up learning from data it is not supposed to have access to, giving it an unfair advantage. While the model may appear to perform exceptionally well during evaluation, its apparent success is often misleading because it has already “seen” aspects of the test data. In real-world scenarios, where new data is unseen, the model may perform far worse than expected.</p><p>A classic example of data leakage is when you are predicting customer churn, and one of the features you include is the total amount billed, which may have been calculated after the observation period. If your model has access to this post-event information during training, it can easily “guess” the churn outcome, but this knowledge would not be available when predicting churn for new customers. Another subtle form of leakage can happen during preprocessing: if you calculate scaling parameters, impute missing values, or encode categories using the entire dataset — including the test data — you unintentionally provide the model with insights about the unseen data.</p><p>Data leakage is particularly dangerous because it can give a false sense of confidence in your model’s accuracy. A model that appears to achieve perfect or near-perfect performance due to leakage will likely fail in production. This can lead to poor business decisions, wasted resources, and a lack of trust in machine learning outputs. It is therefore critical to identify potential sources of leakage before training and evaluation.</p><p>The most effective way to prevent data leakage is to <strong>split your dataset into training and testing sets at the very beginning</strong> of your workflow. Once the split is done, all preprocessing, feature engineering, and parameter fitting should be carried out <strong>exclusively on the training data</strong>. Only after these transformations have been learned from the training set should you apply the same procedures to the test set, using the same parameters. This ensures that the test set remains a true representation of unseen data, allowing your evaluation metrics to accurately reflect the model’s ability to generalize.</p><p>In summary, understanding and preventing data leakage is crucial for building trustworthy machine learning models. By carefully separating training and test data and strictly applying preprocessing learned from the training set, you ensure that your model’s performance is genuine and that it can be expected to perform reliably on new, real-world data.</p><h3>5. Handle Outliers</h3><p>Outliers are extreme values that deviate significantly from the rest of the data. They are often the result of measurement errors, data entry mistakes, or rare events, and if left unaddressed, they can distort your model’s understanding of patterns. Models that are sensitive to extreme values, such as linear regression or distance-based algorithms, can be heavily influenced by outliers, leading to poor predictions and reduced generalization performance. Therefore, detecting and appropriately handling outliers is a crucial step in preprocessing.</p><p>One of the most effective ways to identify outliers is through <strong>visualization</strong>. Visual tools allow you to intuitively spot unusual points and understand the distribution of your data. Among these, the <strong>box plot</strong> is a particularly powerful method. Box plots display the median, quartiles, and potential outliers in a dataset, giving a clear view of the spread and skewness of your data. Points that lie beyond the whiskers of the box plot are typically considered outliers and warrant further investigation.</p><p>Once outliers are identified, the next step is deciding how to handle them. There are generally two approaches: <strong>clipping</strong> and <strong>removal</strong>. Clipping involves setting extreme values to a threshold, effectively limiting their influence without losing the data entirely. This is useful when outliers are genuine but disproportionately large or small compared to the rest of the data. Removal, on the other hand, involves deleting rows that contain extreme values, which can be appropriate when outliers are clearly erroneous or irrelevant. The choice between clipping and removal depends on the context, the nature of the data, and the specific goals of the analysis.</p><p>Handling outliers thoughtfully can make your model more robust and prevent it from being unduly influenced by rare events. By combining visualization techniques like box plots with deliberate decisions about clipping or removal, you ensure that your models learn meaningful patterns from the majority of the data while minimizing the impact of anomalous points. This step not only improves model performance but also contributes to more reliable and interpretable results.</p><h3>6. Exploratory Data Analysis (EDA)</h3><p>Exploratory Data Analysis, or EDA, is a critical step in the machine learning workflow that involves examining your dataset to understand its underlying characteristics. The goal of EDA is to uncover patterns, detect anomalies, and identify relationships among features, which in turn guides preprocessing, feature engineering, and modeling decisions. While it is not always mandatory, performing EDA thoughtfully can save significant time and effort later by revealing potential issues before training a model.</p><p>EDA can be approached at several levels. <strong>Univariate analysis</strong> focuses on individual features, examining their distributions, central tendencies, and variability. This helps in understanding whether a feature is skewed, contains outliers, or has an unexpected spread of values. <strong>Bivariate analysis</strong> goes a step further by exploring relationships between two variables, often between features and the target variable. This can help identify predictive relationships or correlations that might be important for modeling. <strong>Multivariate analysis</strong> considers interactions among multiple features simultaneously, which is particularly useful for complex datasets where feature combinations may influence the target variable in ways not apparent when looking at features in isolation.</p><p>Performing EDA allows you to spot potential problems early, such as highly imbalanced classes, correlated features, or unexpected data distributions. It also helps in making informed decisions about transformations, encoding, or feature selection. In essence, EDA is a form of detective work: by systematically exploring your data, you build a deeper understanding of its structure, patterns, and quirks, laying a strong foundation for all subsequent preprocessing and modeling steps.</p><h3>7. Check Assumptions and Multicollinearity</h3><p>Certain machine learning models, particularly parametric models like linear regression, rely on specific assumptions about the underlying data. One key assumption is that the features follow a <strong>roughly normal distribution</strong>, which helps ensure that the model can estimate relationships accurately and that its predictions are reliable. While not all models require normally distributed data, understanding the distribution of your features is an important step in preprocessing, as transformations or scaling may be necessary to meet these assumptions.</p><p>Another critical consideration is <strong>multicollinearity</strong>, which occurs when two or more features are highly correlated with each other. High multicollinearity can distort model coefficients, reduce interpretability, and make the model unstable, as small changes in the data can lead to large fluctuations in the estimated relationships. Checking for multicollinearity ensures that each feature provides <strong>independent information</strong> to the model, improving both the stability and interpretability of the results. Common methods for detecting multicollinearity include examining correlation matrices or calculating variance inflation factors (VIFs), which highlight features that may need to be removed or combined.</p><p>By assessing assumptions and multicollinearity, you not only improve the performance of models like linear regression but also gain confidence that your data is structured in a way that allows meaningful patterns to be learned. Addressing these considerations early in the preprocessing pipeline prevents problems downstream and sets the stage for more robust, interpretable, and reliable models.</p><h3>8. Scaling and Normalization</h3><p>Many machine learning algorithms are sensitive to the scale of input features, meaning that features with larger ranges can disproportionately influence the model’s learning process. For example, a feature representing income in thousands might dominate over a feature representing age in tens if both are fed into the model without adjustment. To address this, <strong>scaling</strong> is often applied to bring numeric features to a common range or variance. This ensures that each feature contributes more evenly to the learning process, preventing any single feature from overwhelming the others.</p><p>In addition to scaling, <strong>normalization</strong> is another important preprocessing step. Normalization transforms data so that features contribute equally to the model, often by adjusting them to a common scale or converting them into probabilities. This is particularly important for algorithms that rely on distance calculations, such as k-nearest neighbors, clustering methods, and support vector machines, as well as for models that use gradient-based optimization, like logistic regression and neural networks.</p><p>By carefully scaling and normalizing your features, you ensure that your model can learn efficiently and that the resulting predictions are balanced and meaningful. Neglecting this step can lead to models that converge slowly, become biased toward certain features, or fail to capture the true patterns in the data. Ultimately, scaling and normalization are essential steps in preparing your dataset for robust and reliable machine learning.</p><h3>Conclusion</h3><p>Data preprocessing is the backbone of successful machine learning. From understanding your dataset and handling missing values, to preventing data leakage, managing outliers, and scaling features, each step ensures your model learns meaningful patterns rather than noise.</p><p>Remember: <strong>a clean, well-prepared dataset is often more valuable than a complex model</strong>. Treat preprocessing as a fundamental part of your ML workflow, and your models will thank you.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ad5959c8bc64" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Retrieval-Augmented Generation (RAG) Chatbot with Python]]></title>
            <link>https://medium.com/@crimsonsyrus000/building-a-retrieval-augmented-generation-rag-chatbot-with-python-bf324335e335?source=rss-ce6ac9595b76------2</link>
            <guid isPermaLink="false">https://medium.com/p/bf324335e335</guid>
            <dc:creator><![CDATA[Crimsonsyrus]]></dc:creator>
            <pubDate>Thu, 13 Mar 2025 10:02:46 GMT</pubDate>
            <atom:updated>2025-03-13T10:27:01.772Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Why Do We Need RAG?</strong></p><p>Large Language Models (LLMs) like GPT-4 are incredibly powerful, but they have a <strong>fixed knowledge cutoff</strong>. This means they do not have information beyond a certain date, and they lack access to <strong>domain-specific</strong> knowledge unless explicitly trained on it.</p><p>One way to provide an LLM with new knowledge is <strong>retraining</strong>, but this is <strong>expensive and impractical</strong> for most use cases. Instead, <strong>Retrieval-Augmented Generation (RAG)</strong> dynamically retrieves relevant context from external sources, making responses more <strong>accurate, domain-specific, and context-aware</strong> without modifying the LLM itself.</p><p><strong>How RAG Works</strong></p><ol><li>The user asks a question.</li><li>An information retrieval mechanism searches for relevant information.</li><li>The retrieved information is merged with the user’s question.</li><li>The AI generates a response using the augmented query.</li><li>The final answer is delivered to the user.</li></ol><p><strong>How to Build a RAG Chatbot</strong></p><p><em>Collecting Data</em></p><ol><li>The chatbot requires a <strong>domain-specific knowledge base</strong> (e.g., legal documents, medical reports, financial records).</li><li>These documents can come from various sources: PDFs, websites, databases, or structured files like CSV/JSON.</li><li>The system processes and cleans the documents to make them easily searchable.</li></ol><p><em>Creating the Knowledge Base</em></p><ol><li>Break documents into smaller chunks for better searchability.</li><li>Convert each chunk into an embedding (numerical representation).</li><li>Store the embeddings in a vector database (e.g., Pinecone, FAISS, Weaviate).</li></ol><p><em>Retrieving Relevant Information</em></p><ol><li>Convert the user query into an embedding.</li><li>Search the vector database for similar embeddings.</li><li>Retrieve the most relevant text chunks as context.</li></ol><p><em>Augmenting the User Query</em></p><ol><li>The retrieved information is merged with the user’s original question.</li><li>This ensures the AI has <strong>additional knowledge</strong> before generating an answer.</li></ol><p><em>Generating the Response</em></p><ol><li>The enhanced query is sent to an LLM (e.g., OpenAI’s GPT-4).</li><li>The model processes both the original query and the retrieved knowledge before crafting a response.</li></ol><p><em>Delivering the Response</em></p><ol><li>The chatbot presents a <strong>more accurate and fact-based answer</strong> to the user.</li><li>The response can be displayed in a chatbot interface, web app, or voice assistant.</li></ol><p><strong>Implementing a RAG Chatbot in Python</strong></p><p>We will build a simple RAG chatbot using:</p><ol><li><strong>Pinecone</strong> → Vector Database (stores &amp; retrieves embeddings)</li><li><strong>OpenAI API</strong> → Generates responses using a language model</li></ol><pre>from dotenv import load_dotenv<br>import os<br>from pinecone import Pinecone<br>from openai import OpenAI<br>from pdfminer.high_level import extract_text<br>from sentence_transformers import SentenceTransformer<br>from langchain.text_splitter import RecursiveCharacterTextSplitter<br><br># Load environment variables from a .env file<br># These variables contain sensitive information like API keys needed to connect with external services.<br>load_dotenv()<br><br># Initialize a pre-trained model that converts text into a numerical format (embedding)<br># This is necessary for comparing text similarities using vector search.<br>embedding_model = SentenceTransformer(&#39;all-MiniLM-L6-v2&#39;)<br><br># Connect to Pinecone, a specialized database designed for storing and retrieving vector embeddings.<br># The connection is established using an API key stored in the environment variables.<br>pc = Pinecone(api_key=os.getenv(&#39;PINECONE_API_KEY&#39;))<br><br># Access a specific Pinecone index where all the stored embeddings will be saved and retrieved.<br># An index is like a structured database table optimized for searching similar vectors.<br>index = pc.Index(os.getenv(&#39;PINECONE_INDEX&#39;))<br><br># Initialize the OpenAI client, which allows us to send queries to OpenAI&#39;s models for generating responses.<br>openaiClient = OpenAI()<br><br>def get_embedding(text):<br>    &quot;&quot;&quot;<br>    Converts the input text into a high-dimensional vector representation.<br>    This numerical representation allows us to measure similarities between different pieces of text.<br>    :param text: The input text to be converted.<br>    :return: A numerical vector representing the meaning of the input text.<br>    &quot;&quot;&quot;<br>    return embedding_model.encode(text)<br><br>def process_pdf(file_path):<br>    &quot;&quot;&quot;<br>    Extracts raw text from a PDF file, splits it into smaller meaningful chunks, <br>    converts them into embeddings, and stores these embeddings in the Pinecone vector database.<br>    :param file_path: The location of the PDF file to process.<br>    &quot;&quot;&quot;<br>    # Extract textual content from the given PDF file<br>    text = extract_text(file_path)<br>    <br>    # Split the extracted text into smaller chunks for better indexing<br>    # The `chunk_size` determines how large each segment is (500 characters per chunk)<br>    # `chunk_overlap` ensures slight overlap (50 characters) to maintain context continuity.<br>    text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)<br>    chunks = text_splitter.split_text(text)<br><br>    # Process each chunk separately: Convert it into a numerical vector and store it in Pinecone.<br>    for i, chunk in enumerate(chunks):<br>        embedding = get_embedding(chunk)  # Generate the vector representation for the chunk.<br>        index.upsert(vectors=[{&quot;id&quot;: str(i), &quot;values&quot;: embedding, &quot;metadata&quot;: {&quot;text&quot;: chunk}}])<br><br>def query_rag_system(query_text):<br>    &quot;&quot;&quot;<br>    Uses a query to find the most relevant text chunk from the Pinecone database,<br>    then sends this context to OpenAI to generate an appropriate response.<br>    :param query_text: The user&#39;s question or input.<br>    :return: AI-generated response based on relevant retrieved information.<br>    &quot;&quot;&quot;<br>    # Convert the query into an embedding vector so we can find similar stored embeddings.<br>    query_embedding = get_embedding(query_text)<br>    <br>    # Search Pinecone for the most relevant text chunk<br>    # Parameters:<br>    # - vector: The embedding of the query text (must be a list of numerical values).<br>    # - top_k: The number of most similar results to retrieve (1 means fetch the closest match).<br>    # - include_metadata: Ensures the stored metadata (text chunk) is retrieved with the match.<br>    results = index.query(vector=query_embedding.tolist(), top_k=1, include_metadata=True)<br>    <br>    # Extract the best-matching text chunk from Pinecone&#39;s response.<br>    context = results[&quot;matches&quot;][0].get(&quot;metadata&quot;, {}).get(&quot;text&quot;, &quot;&quot;)<br>    <br>    # Construct a well-formatted prompt using the retrieved context.<br>    # This ensures OpenAI generates responses based on relevant information.<br>    prompt = f&quot;Context: {context}\n\nQuestion: {query_text}\nAnswer:&quot;<br>    <br>    # Send the query to OpenAI along with the retrieved context, asking it to generate an answer.<br>    # Parameters:<br>    # - model: The specific AI model to use (e.g., &#39;gpt-4o-mini&#39;).<br>    # - messages: A list of messages forming the conversation context. The last message is the user query.<br>    response = openaiClient.chat.completions.create(<br>        model=&quot;gpt-4o-mini&quot;,<br>        messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: prompt}]<br>    )<br>    <br>    # Extract and return the generated response in a clean format.<br>    return response.choices[0].message.content.strip()<br><br># Example Usage:<br># Step 1: Process a PDF file and store its content in Pinecone as embeddings.<br>process_pdf(&quot;Dinosaurs_in_India.pdf&quot;)</pre><p>You can serve this system with an API built using Flask or Python. This is what I did and upon querying using a simple frontend I got the following results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*j548Ck24GFjs6NYF3P7bnw.png" /><figcaption>Conversation with the RAG Chatbot</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf324335e335" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Converting HTML to PDF (such that locally stored images are displayed) using Puppeteer and Nodejs]]></title>
            <link>https://medium.com/@crimsonsyrus000/converting-html-to-pdf-such-that-locally-stored-images-are-displayed-using-puppeteer-and-nodejs-94f53393c6f8?source=rss-ce6ac9595b76------2</link>
            <guid isPermaLink="false">https://medium.com/p/94f53393c6f8</guid>
            <dc:creator><![CDATA[Crimsonsyrus]]></dc:creator>
            <pubDate>Mon, 24 Apr 2023 16:04:59 GMT</pubDate>
            <atom:updated>2023-04-24T16:04:59.691Z</atom:updated>
            <content:encoded><![CDATA[<p>I like to keep things short, so I’ll start right away.</p><p>Create a new folder named htmlToPDF (or anything you want). CD into it. Then, run the following to initialize a new npm project with default options:</p><pre>npm init -y</pre><p>Now, let’s install Puppeteer:</p><pre>npm install --save puppeteer</pre><p>Create a new file named index.js and add the following snippet:</p><pre>const puppeteer = require(&#39;puppeteer&#39;);<br>const fs = require(&#39;fs&#39;);<br>const path = require(&#39;path&#39;);</pre><p>The necessary modules have been imported here.</p><p>Read the HTML file next:</p><pre>    const htmlFilePath = path.join(__dirname, &#39;index.html&#39;);<br>    let html = fs.readFileSync(htmlFilePath, &#39;utf8&#39;);</pre><p>The absolute path of index.html is being read into the htmlFilePath variable. We’re using Nodejs’s ‘path’ module to join the path to the current working directory given by ‘__dirname’ with the actual filename to get the absolute path.</p><p>Then we’re using the ‘fs’ module to synchronously read the HTML file into the html variable.</p><p>Now, to one of the most important parts, i.e., rendering images stored locally. For that we need to use the image’s base64 form. If we directly use the path of the image, it is not displayed.</p><p>“It’s a security block to protect from malicious websites to access local images”, as <a href="https://github.com/yujiosaka">yujiosaka</a> commented on the github issue regarding the same.</p><p>Below we are replacing the ‘src’ of the &lt;img&gt; that we want to be displayed in the PDF with it’s base64 string.</p><p>First, we read the image using the ‘fs’ module. fs.readFileSync() here returns a buffer of the contents of ‘image.png’ which is then converted to a base64-encoded string using the toString(‘base64’) method.</p><p>Here, process.cwd() returns the path to the current working directory. Also note the double slashes. The reason for putting the additional slash is that to use a backslash character in a string literal in JavaScript, we need to escape it by using another backslash</p><pre>html = html.replace(&#39;image.png&#39;, `data:image/png;base64,${fs.readFileSync(`${process.cwd()}\\image.png`).toString(&#39;base64&#39;)}`);</pre><p>Now the actual conversion part arrives:</p><pre>const browser = await puppeteer.launch();<br>const page = await browser.newPage();<br>await page.setContent(html);<br><br>// generate PDF from HTML<br>const pdf = await page.pdf({ <br>  format: &#39;Letter&#39;,<br>  path: &#39;my_pdf.pdf&#39;<br>});<br><br>// close the browser instance<br>await browser.close();</pre><p>const browser = await puppeteer.launch();:</p><p>This line launches a new instance of the Puppeteer browser using the puppeteer.launch() method. The launch() method returns a Promise that resolves to a Browser object, which represents a running instance of the Chromium browser. The await keyword is used to wait for the Promise to resolve before proceeding to the next line of code.</p><p>const page = await browser.newPage();</p><p>This line creates a new page in the Chromium browser instance using the browser.newPage() method. The newPage() method returns a Promise that resolves to a Page object, which represents a single tab in the browser.</p><p>await page.setContent(html);</p><p>This line sets the content of the page to the HTML code contained in the html variable. The setContent() method is an asynchronous method that takes a string of HTML code as its argument and returns a Promise that resolves when the HTML is successfully loaded into the page.</p><p>const pdf = await page.pdf({ format: &#39;Letter&#39;, path: &#39;my_pdf.pdf&#39; });:</p><p>This line generates a PDF document from the HTML content of the page using the page.pdf() method and writes it to the path specified, which would be the current directory in this case. The pdf() method takes an options object as its argument, which can include options such as the format of the PDF document (e.g., &quot;Letter&quot;, &quot;A4&quot;, etc.), the orientation of the document (e.g., &quot;portrait&quot; or &quot;landscape&quot;), and other settings. The pdf() method returns a Promise that resolves to a Buffer containing the PDF document data.</p><p>In summary, the code launches a new instance of the Chromium browser, creates a new page in the browser, sets the content of the page to the HTML code contained in the html variable, generates a PDF document from the HTML content of the page, and assigns the resulting PDF Buffer to the pdf variable. And finally, closes the browser instance.</p><p>Ultimately, index.js will look like:</p><pre>// import the necessary modules<br>const puppeteer = require(&#39;puppeteer&#39;);<br>const fs = require(&#39;fs&#39;);<br>const path = require(&#39;path&#39;);<br><br>// read the HTML file<br>const htmlFilePath = path.join(__dirname, &#39;index.html&#39;);<br>let html = fs.readFileSync(htmlFilePath, &#39;utf8&#39;);<br><br>const convertToPDF = async (html) =&gt; {<br><br>  // replace local image src with it&#39;s base64 string<br>  html = html.replace(&#39;image.png&#39;, `data:image/png;base64,${fs.readFileSync(`${process.cwd()}\\image.png`).toString(&#39;base64&#39;)}`);<br><br>  // launch browser instance <br>  const browser = await puppeteer.launch();<br><br>  // create a new page/tab<br>  const page = await browser.newPage();<br><br>  // set the content of the new page<br>  await page.setContent(html);<br><br>  // Generate PDF from HTML<br>  const pdf = await page.pdf({ <br>    format: &#39;Letter&#39;,<br>    path: &#39;my_pdf.pdf&#39;<br>  });<br><br>  // close the browser instance<br>  await browser.close();<br>}<br><br>convertToPDF(html);</pre><p>I’ve put the code in an ‘async’ function as the ‘await’ keyword can only be used inside of an ‘async’ function.</p><p>So, we’re done here. Thanks for the read.</p><p>Your feedback will be appreciated.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=94f53393c6f8" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>