Monday, December 30, 2024

How to run mongo atlas vector DB locally?

Its pretty much easy, 

docker pull mongodb/mongodb-atlas-local:latest

docker run -p 27017:27017 mongodb/mongodb-atlas-local


Below is connection string without and with authentication 

mongosh "mongodb://localhost:27017/?directConnection=true"

mongosh "mongodb://user:pass@localhost:27017/?directConnection=true"


references:

https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-docker/

How to create a docker image and push to Docker hub

Have Dockerfile like this below 

================================

# Use Python 3.12 as the base image

FROM python:3.12-slim

# Set the working directory

WORKDIR /app

# Copy the requirements and install dependencies

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code

COPY ./app ./app

# Expose the application port

EXPOSE 8000

# Start the FastAPI app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]


Have requirement.txt file like below 

=====================================

fastapi

uvicorn


Have the main.py like below 

===========================

from fastapi import FastAPI

app = FastAPI()

@app.get("/")

def read_root():

    return {"message": "Hello, Dockerized FastAPI!"}


Now the build process is like below 

The below steps to be executed after creating an account in docker hub. Instead of using password on terminal, access token can be created and that can be used. 


docker build -t mrrathish/crashing-docker-app:latest .

docker login

docker push mrrathish/crashing-docker-app:latest


docker run -p 8000:8000 mrrathish/crashing-docker-app:latest

That's it pretty much 


Friday, December 27, 2024

What is Container , Container Image

A container is run from a container image.

A container image is a static version of all the files, environment variables, and the default command/program that should be present in a container. Static here means that the container image is not running, it's not being executed, it's only the packaged files and metadata.

In contrast to a "container image" that is the stored static contents, a "container" normally refers to the running instance, the thing that is being executed.

When the container is started and running (started from a container image) it could create or change files, environment variables, etc. Those changes will exist only in that container, but would not persist in the underlying container image (would not be saved to disk).

A container image is comparable to the program file and contents, e.g. python and some file main.py.

And the container itself (in contrast to the container image) is the actual running instance of the image, comparable to a process. In fact, a container is running only when it has a process running (and normally it's only a single process). The container stops when there's no process running in it.

A container image normally includes in its metadata the default program or command that should berun when the container is started and the parameters to be passed to that program. Very similar to what would be if it was in the command line.

When a container is started, it will run that command/program (although you can override it and make it run a different command/program).

A container is running as long as the main process (command or program) is running.

A container normally has a single process, but it's also possible to start subprocesses from the main process, and that way you will have multiple processes in the same container.

But it's not possible to have a running container without at least one running process. If the main process stops, the container stops.

references:

https://fastapi.tiangolo.com/deployment/docker/#what-is-a-container-image

Sunday, December 22, 2024

How to use Pedantic to declare JSON data models (Data shapes)

 First, you need to import BaseModel from pydantic and then use it to create subclasses defining the schema, or data shapes, you want to receive.

Next, you declare your data model as a class that inherits from BaseModel, using standard Python types for all the attributes:

# main.py

from typing import Optional

from fastapi import FastAPI

from pydantic import BaseModel


class Item(BaseModel):

    name: str

    description: Optional[str] = None

    price: float

    tax: Optional[float] = None


app = FastAPI()


@app.post("/items/")

async def create_item(item: Item):

    return item


When a model attribute has a default value, it is not required. Otherwise, it is required. To make an attribute optional, you can use None.


For example, the model above declares a JSON object (or Python dict) like this:


{

    "name": "Foo",

    "description": None,

    "price": 45.2,

    "tax": None

}


In this case, since description and tax are optional because they have a default value of None, this JSON object would also be valid:


{

    "name": "Foo",

    "price": 45.2

}


A JSON object that omits the default values is also valid.

Next, add the new pydantic model to your path operation as a parameter. You declare it the same way you declared path parameters:



# main.py


from typing import Optional


from fastapi import FastAPI

from pydantic import BaseModel


class Item(BaseModel):

    name: str

    description: Optional[str] = None

    price: float

    tax: Optional[float] = None


app = FastAPI()


@app.post("/items/")

async def create_item(item: Item):

    return item

The parameter item has a type hint of Item, which means that item is declared as an instance of the class Item.

With that Python type declaration, FastAPI will:

Read the body of the request as JSON

Convert the corresponding types if needed

Validate the data and return a clear error if it is invalid

Give you the received data in the parameter item—since you declared it to be of type Item, you will also have all the editor support, with completion and type checks for all the attributes and their types

By using standard type hints with pydantic, FastAPI helps you build APIs that have all these best practices by default, with little effort.


References:

https://realpython.com/fastapi-python-web-apis/#create-a-first-api


Unicorn and alternatives

What is Uvicorn?

Uvicorn is a lightning-fast ASGI (Asynchronous Server Gateway Interface) server designed to run Python web applications. It supports asynchronous frameworks like FastAPI, Starlette, and others. Uvicorn is built on top of uvloop and httptools, providing excellent performance for handling concurrent requests in modern web applications.

Why is Uvicorn Required for FastAPI?

FastAPI is an ASGI framework, meaning it requires an ASGI server to handle HTTP requests and serve the application. Uvicorn is a popular choice because:

Asynchronous Support: It natively supports async features, which are central to FastAPI’s high-performance capabilities.

Performance: Uvicorn is optimized for speed and can efficiently handle a large number of concurrent requests.

Compatibility: Uvicorn is fully compatible with FastAPI and provides seamless integration.

Ease of Use: It's simple to install and use, with minimal configuration required.

Without a server like Uvicorn, FastAPI can't process incoming HTTP requests or serve responses.


Alternatives to Uvicorn

There are other ASGI servers available that can be used instead of Uvicorn. Here are some common alternatives:

Daphne

Developed by the Django Channels team.

Suitable for applications that require WebSocket support or compatibility with Django Channels.

Less performant than Uvicorn in general cases.

Command


daphne myapp:app

Hypercorn


A highly configurable ASGI server.

Supports multiple protocols, including HTTP/1, HTTP/2, WebSocket, and QUIC.

A good alternative if fine-grained control over server behavior is needed.

Command:


hypercorn myapp:app

ASGI Built-in Development Server

Some ASGI frameworks come with built-in development servers for local testing.

Not recommended for production.




Saturday, December 21, 2024

What is dependency Injection in Python

Dependency Injection (DI) in Python is a design pattern where the dependencies of a class or function are provided (injected) from the outside, rather than being created or managed by the class or function itself. This approach makes the code more modular, testable, and easier to maintain.


Key Concepts

Dependency: Any external object or resource that a class or function needs to operate (e.g., a database connection, an API client, a logger).

Injection: Supplying the dependency from outside the class or function, typically as an argument.

Why Use Dependency Injection?

Decoupling: Reduces tight coupling between components.

Testability: Makes it easier to test classes or functions by providing mock or stub dependencies.

Flexibility: Allows swapping out dependencies without modifying the dependent class or function.

Example Without Dependency Injection

Here, the dependency (Logger) is created inside the Service class, which tightly couples the two.


class Logger:

    def log(self, message):

        print(f"LOG: {message}")


class Service:

    def __init__(self):

        self.logger = Logger()  # Dependency is created inside the class


    def perform_task(self):

        self.logger.log("Task performed")


Example With Dependency Injection

In this example, the Logger is injected into the Service class, making the Service independent of how the Logger is implemented.


python

Copy code




class Logger:

    def log(self, message):

        print(f"LOG: {message}")


class Service:

    def __init__(self, logger):  # Dependency is injected

        self.logger = logger


    def perform_task(self):

        self.logger.log("Task performed")


# Injecting the dependency

logger = Logger()

service = Service(logger)

service.perform_task()


Benefits

The Service class does not need to know how the Logger is implemented.

You can easily swap out Logger for another implementation (e.g., FileLogger, DatabaseLogger) without modifying Service.

Dependency Injection with Frameworks

In larger applications, dependency injection frameworks (like Dependency Injector or pytest fixtures in testing) can help manage dependencies systematically.


from dependency_injector import containers, providers


class Logger:

    def log(self, message):

        print(f"LOG: {message}")


class Service:

    def __init__(self, logger):

        self.logger = logger


    def perform_task(self):

        self.logger.log("Task performed")


# DI container

class Container(containers.DeclarativeContainer):

    logger = providers.Factory(Logger)

    service = providers.Factory(Service, logger=logger)


# Using the container

container = Container()

service = container.service()

service.perform_task()



1. Providers

Providers are objects responsible for creating and managing dependencies. They can define how objects (dependencies) are created and supply these objects to other parts of the application.


Providers can:


Instantiate objects.

Return pre-configured instances.

Manage singletons (single instances reused across the application).

Provide factories for creating new instances on demand.

Types of Providers

Here are some commonly used provider types in Dependency Injector:


Factory: Creates a new instance of an object every time it's called.

Singleton: Creates and returns the same instance for every call.

Callable: Calls a specified function or callable.

Configuration: Provides values from an external configuration source (e.g., environment variables, files).

Delegate: Delegates provisioning to another provider.

Resource: Manages external resources with lifecycle hooks like initialization and cleanup


from dependency_injector import providers


# Factory provider (creates a new instance each time)

class Logger:

    def log(self, message):

        print(f"LOG: {message}")


logger_provider = providers.Factory(Logger)

logger_instance_1 = logger_provider()

logger_instance_2 = logger_provider()


print(logger_instance_1 is logger_instance_2)  # False (new instance each time)


# Singleton provider (creates one instance only)

singleton_logger = providers.Singleton(Logger)

logger_instance_3 = singleton_logger()

logger_instance_4 = singleton_logger()


print(logger_instance_3 is logger_instance_4)  # True (same instance)


Sunday, December 15, 2024

Create Embedding Model that adds additional context

 1. Conceptual Understanding

Core Idea: You want to create embeddings that not only represent the input text but also incorporate external knowledge or context. This can significantly improve the quality of similarity searches and downstream tasks.

Example: Imagine you're embedding product descriptions. Adding context like brand, category, or even user purchase history can lead to more relevant recommendations.

2. Methods

Concatenation:

Approach:

Obtain Context Embeddings: Generate embeddings for the context information (e.g., brand, category) using the same or a different embedding model.

Concatenate: Concatenate the context embeddings with the input text embeddings.

Example:

Input: "Comfortable shoes"

Context: "Brand: Nike, Category: Running"

Embedding: embed("Comfortable shoes") + embed("Brand: Nike") + embed("Category: Running")

Weighted Sum:

Approach:

Obtain embeddings as in concatenation.

Assign weights to each embedding based on its importance.

Calculate a weighted sum of the embeddings.

Example:

weighted_embedding = 0.7 * embed("Comfortable shoes") + 0.2 * embed("Brand: Nike") + 0.1 * embed("Category: Running")

Contextualized Embeddings:

Approach:

Use a language model (like BERT or GPT) to generate embeddings.

Feed the input text and context to the model simultaneously.

The model will generate embeddings that capture the interaction between the text and the context.

Implementation: Utilize Hugging Face Transformers library for easy access to pre-trained models.

3. Implementation Example (Concatenation with Sentence-Transformers)


Python


from sentence_transformers import SentenceTransformer


model = SentenceTransformer('all-MiniLM-L6-v2') 


def embed_with_context(text, context):

    """

    Generates embeddings for the input text with added context.


    Args:

        text: The input text.

        context: A dictionary containing context information.


    Returns:

        The concatenated embedding.

    """

    text_embedding = model.encode(text)

    context_embeddings = [model.encode(f"{key}: {value}") for key, value in context.items()]

    return np.concatenate([text_embedding] + context_embeddings)


# Example Usage

input_text = "Comfortable shoes"

context = {"Brand": "Nike", "Category": "Running"}

embedding = embed_with_context(input_text, context)

4. Key Considerations


Context Representation: Choose a suitable format for representing context (dictionaries, lists, etc.).

Embedding Model: Select an embedding model that aligns with your context and task.

Weighting: Experiment with different weighting schemes for optimal results.

Evaluation: Thoroughly evaluate the performance of your custom embeddings on your specific task.

Remember: The effectiveness of your custom embeddings will depend on the quality and relevance of the context information you provide. Experiment with different approaches and carefully evaluate the results to find the best solution for your use case.