Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions CHANGELOG.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we update the changelog with every PR on the package? I thought we just did one changelog update per release, and looked back at the merged PRs when writing it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soz haven't had to think about CHANGELOGs in a while as that's done for me on scanner ;)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will have a look later today

Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Added `FastEmbedVectoriser` as a lightweight local embedding backend for HuggingFace-compatible sentence embedding models.
- Added a `fastembed` optional dependency group and documented the lighter install path.

## [v1.1.1] - 2026-07-08

### Fixed

- Resolved bug in VectorStore.from_filespace when loading in a VectorStore created by classifai v1.0.0.
- v1.1.1 VectorStore.from_filespace now assigns the default batch_size if a pre v1.1.0 VectorStore is loaded.

- v1.1.1 VectorStore.from_filespace now assigns the default batch_size if a pre v1.1.0 VectorStore is loaded.

## [v1.1.0] - 2026-07-03

Expand All @@ -33,16 +39,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Resolved reverse search Error when no matched Documents


## [v1.0.0] - 2026-03-27

### Added

- AI Agents - Hooks for using genai to perform tasks on VectorStore results.
- Hooks Framework - new framework for hooks to support premade and custom hook development.
- Server Class Features:
- new methods for instantiating the FastAPI application and/or routing.
- allows middleware to be used, or the routing to be attached to another FastAPI service.
- new methods for instantiating the FastAPI application and/or routing.
- allows middleware to be used, or the routing to be attached to another FastAPI service.
- Documentation - new QuartoDocs documenting the ClassifAI package and new demo notebooks.
- Partial String matching - reverse search VectorStore method now does optional partial matching.
- Vectoriser Class - More options for instantiating HuggingFace models.
Expand All @@ -53,10 +58,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Documentation - better docstrings and updated demo notebooks.
- Dataclasses - updated for more intuitive dataframe column naming.
- Server Class Refactor:
- expanded scope of features.
- renamed start_api method to run_server.
- expanded scope of features.
- renamed start_api method to run_server.

### Fixed

- Server hook data - hook metadata now returned in FastAPI responses.
- Reverse Search results - fixed issue where max_n_results defaulted to None causing errors.

Expand Down Expand Up @@ -84,9 +90,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- REST API - FastAPI served with Uvicorn
- Documentation and Demo - README and Jupyter Notebook minimal demo with fake dataset.


<!-- Links to tags -->

[v1.0.0]: https://github.com/datasciencecampus/classifai/compare/v0.2.1...v1.0.0
[v0.2.1]: https://github.com/datasciencecampus/classifai/compare/v0.2.0...v0.2.1
[v0.2.0]: https://github.com/datasciencecampus/classifai/compare/v0.1.0...v0.2.0
[v0.1.0]: https://github.com/datasciencecampus/classifai/releases/tag/v0.1.0
[v0.1.0]: https://github.com/datasciencecampus/classifai/releases/tag/v0.1.0
3 changes: 2 additions & 1 deletion DEMO/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ add as project dependency:

`pip install "classifai[<dependency>]"`

where `<dependency>` is one or more of `huggingface`,`gcp`,`ollama`, or `all` to install all of them.
where `<dependency>` is one or more of `huggingface`,`fastembed`,`gcp`,`ollama`, or `all` to install all of them.
Use `fastembed` for the lightest local sentence-embedding install path.

##### Using uv

Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Use cases:

Key Features of the package include:

- Use included vectorisers (including Google Cloud, Huggingface and Ollama embedders) or implement your own
- Use included vectorisers (including FastEmbed, Google Cloud, Huggingface and Ollama embedders) or implement your own
- Built in support for custom hook logic - choose from a library of pre-processing and post-processing functions that control the flow of data (spell checking, results deduplication, etc) or write your own hooks
- Deploy Easily with FastAPI - Deploy your semantic search classifier with FastAPI capabilities built into the package for easy REST API deployment

Expand Down Expand Up @@ -72,9 +72,10 @@ The comparison on other aspects, such as per-request speed or hardware requireme
## Installation

You can install the package directly from GitHub in your Python environment, using your preferred package manager.
By default, only the minimum dependencies of the base versions will be installed; you must specify
By default, only the minimum dependencies of the base versions will be installed; you must specify
`classifai[all]` to install all sets of optional dependencies, or `classifai[huggingface, ...]` to install one or more specific sets of optional dependencies.
The current sets of optional dependencies are `[all, huggingface, ollama, gcp]`.
The current sets of optional dependencies are `[all, huggingface, fastembed, ollama, gcp]`.
Use `classifai[fastembed]` for a lighter local embedding setup that does not require `torch` or `transformers`.

##### Pip
```bash
Expand All @@ -98,7 +99,7 @@ The size and quality of the knowledgebase dictates the quality of results you ca

#### Step 1: Choose a vectoriser

A vectoriser transforms a query text string into an embedding vector. You can choose from embedding models accessed via HuggingFace, Google or Ollama, or build your own vectoriser.
A vectoriser transforms a query text string into an embedding vector. You can choose from embedding models accessed via HuggingFace, a lighter local FastEmbed backend, Google or Ollama, or build your own vectoriser.

```python
from classifai.vectorisers import HuggingFaceVectoriser
Expand All @@ -111,6 +112,8 @@ vector = vectoriser.transform("Example text to vectorize")
print(vector.shape)
```

If you want a lighter local runtime without `torch` or `transformers`, install `classifai[fastembed]` and use `FastEmbedVectoriser` with the same model name.

#### Step 2: Build a vector store

You provide a knowledgebase of labelled examples (currently only allows data to be provided as a csv) to build a vector store
Expand Down Expand Up @@ -141,7 +144,7 @@ print(results)
#### Step 4: Deploy as a REST API

In addition to using ClassifAI as a local package, you can use it to create / attach to a FastAPI REST API.
You can create a new FastAPI application which you can modify as required, connect it to an existing FastAPI
You can create a new FastAPI application which you can modify as required, connect it to an existing FastAPI
application, or deploy it immediately as an REST API service using `uvicorn`.

```python
Expand Down
4 changes: 3 additions & 1 deletion _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ website:
- docs/vectorisers.base.VectoriserBase.transform.qmd
- section: "Specific Vectorisers"
contents:
- docs/vectorisers.fastembed.FastEmbedVectoriser.qmd
- docs/vectorisers.huggingface.HuggingFaceVectoriser.qmd
- docs/vectorisers.ollama.OllamaVectoriser.qmd
- docs/vectorisers.gcp.GcpVectoriser.qmd
Expand Down Expand Up @@ -131,6 +132,7 @@ quartodoc:
- vectorisers.base
- vectorisers.base.VectoriserBase
- vectorisers.base.VectoriserBase.transform
- vectorisers.fastembed.FastEmbedVectoriser
- vectorisers.huggingface.HuggingFaceVectoriser
- vectorisers.ollama.OllamaVectoriser
- vectorisers.gcp.GcpVectoriser
Expand Down Expand Up @@ -182,4 +184,4 @@ quartodoc:
- evaluation.main.Evaluation
- evaluation.metrics
- evaluation.metrics.Metric
- evaluation.metrics.MetricResult
- evaluation.metrics.MetricResult
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ huggingface = [
"transformers>=4.52.4",
"torch>=2.7.1"
]
fastembed = [
"fastembed>=0.8.0,<1.0.0"
]
ollama = [
"ollama>=0.5.1"
]
Expand All @@ -54,7 +57,7 @@ gcp = [
"gcsfs>=2026.4.0"
]
all = [
"classifai[huggingface,gcp,ollama]"
"classifai[huggingface,fastembed,gcp,ollama]"
]

[tool.ruff]
Expand Down
7 changes: 7 additions & 0 deletions src/classifai/vectorisers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

This module contains the following 'ready-made' classes:

- `FastEmbedVectoriser`: A lightweight wrapper class for generating text
embeddings with FastEmbed's ONNX backend.
- `GcpVectoriser`: A class for embedding text using either Google Cloud Platform's Gemini API or
Gemini Enterprise Agent Platform (formerly VertexAI) API.
- `HuggingFaceVectoriser`: A general wrapper class for Huggingface Transformers
Expand All @@ -37,6 +39,9 @@
Each class is designed to interface with a specific service that provides embedding model
functionality.

The `FastEmbedVectoriser` class utilizes FastEmbed's ONNX backend to load
sentence embedding models without requiring `torch` or `transformers`.

The `GcpVectoriser` class leverages Google's GenAI API,

The `HuggingFaceVectoriser` class utilizes models from the Huggingface Transformers library.
Expand All @@ -56,11 +61,13 @@
"""

from .base import VectoriserBase
from .fastembed import FastEmbedVectoriser
from .gcp import GcpVectoriser
from .huggingface import HuggingFaceVectoriser
from .ollama import OllamaVectoriser

__all__ = [
"FastEmbedVectoriser",
"GcpVectoriser",
"HuggingFaceVectoriser",
"OllamaVectoriser",
Expand Down
5 changes: 5 additions & 0 deletions src/classifai/vectorisers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
This module contains the following 'ready-made' classes:


- `FastEmbedVectoriser`: A lightweight wrapper class for generating text
embeddings with FastEmbed's ONNX backend.
- `GcpVectoriser`: A class for embedding text using Google Cloud Platform's GenAI API.
- `HuggingFaceVectoriser`: A general wrapper class for Huggingface Transformers
models to generate text embeddings.
Expand All @@ -36,6 +38,9 @@
Each class is designed to interface with a specific service that provides embedding model
functionality.

The `FastEmbedVectoriser` class utilizes FastEmbed's ONNX backend to load
sentence embedding models without requiring `torch` or `transformers`.

The `GcpVectoriser` class leverages Google's GenAI API,

The `HuggingFaceVectoriser` class utilizes models from the Huggingface Transformers library.
Expand Down
149 changes: 149 additions & 0 deletions src/classifai/vectorisers/fastembed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""A module that provides a wrapper for FastEmbed models to generate text embeddings."""

import numpy as np

from classifai._optional import check_deps
from classifai.exceptions import ExternalServiceError, VectorisationError

from .base import VectoriserBase


class FastEmbedVectoriser(VectoriserBase):
"""A lightweight wrapper class for generating embeddings with FastEmbed.

The `FastEmbedVectoriser` uses FastEmbed's ONNX backend to generate
embeddings from FastEmbed-compatible sentence embedding models without
requiring `torch` or `transformers` as runtime dependencies. The
`model_name` must be a name recognised by FastEmbed. To see all
supported models, you can run:
`FastEmbedVectoriser.list_supported_models()`

To use a pre-downloaded model in an air-gapped environment, provide both
its official FastEmbed `model_name` and its local directory through
`specific_model_path`. FastEmbed uses `model_name` to identify the model
configuration and `specific_model_path` to locate its local ONNX files.

Attributes:
model_name (str): The official FastEmbed name of the embedding model.
model (fastembed.TextEmbedding): The FastEmbed model instance.
specific_model_path (str | None): The path of the local FastEmbed model.
"""

def __init__(
self,
model_name: str,
specific_model_path: str | None = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider removing this parameter and then user can pass it as part of the kwarg to the constructor?

Looking at how this is handled in the HuggingFaceVectoriser class (HuggingFace also has its own way of local cache checking for models under the hood), we don't have a special parameter in that constructor.

So mirroring that might be good for consistency

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamie-ons added this so not sure of the rationale, don't mind either way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will confirm when @jamie-ons returns from leave, but I think this was to allow someone to specify whether to use ONNX format weights on a model card if there's multiple options available

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HuggingFaceVectoriser allows a user to put in a path to a local model as the model_name, however in fast embed this is not allowed.

I therefore decided to add in this specific model path as a key use of the FastEmbedVectoriser is to run models in enviroments where resources may be lower.

This could often mean that ability to download large packages (torch) or files (model weights) may be restricted. I also don't think the FastEmbed documentation is that clear about how to run models from a local download and so thought adding the argument would save them time in researching how to do it.

I will update the docstrings to make this clearer.

model_kwargs: dict | None = None,
):
"""Initialises the FastEmbedVectoriser with the specified model name.

Args:
model_name (str): The official name of the embedding model for FastEmbed
(e.g., "sentence-transformers/all-MiniLM-L6-v2").
specific_model_path (str | None): The local directory containing
the pre-downloaded ONNX model. To run offline, provide this
value together with the model's official `model_name`.
Defaults to None.
model_kwargs (dict): [optional] Additional keyword arguments to
pass to the model (e.g., `cache_dir`). Defaults to None.

Raises:
`ExternalServiceError`: If the FastEmbed model cannot be loaded.
"""
check_deps(["fastembed"], extra="fastembed")
from fastembed import TextEmbedding # type: ignore

self.model_name = model_name
self.specific_model_path = specific_model_path
model_kwargs = dict(model_kwargs or {})

if self.specific_model_path is not None:
model_kwargs["specific_model_path"] = str(self.specific_model_path)

try:
self.model = TextEmbedding(model_name=self.model_name, **model_kwargs)
except Exception as e:
raise ExternalServiceError(
"Failed to load FastEmbed model.",
context={
"vectoriser": "fastembed",
"model": self.model_name,
"cause": str(e),
"cause_type": type(e).__name__,
},
) from e

def transform(self, texts: str | list[str]) -> np.ndarray:
"""Transforms input text(s) into embeddings using FastEmbed.

Args:
texts (str | list[str]): The input text(s) to embed. Can be a
single string or a list of strings.

Returns:
numpy.ndarray: A 2D array of embeddings, where each row
corresponds to an input text.

Raises:
`VectorisationError`: If FastEmbed fails to generate or parse
embeddings.
"""
# If a single string is passed as arg to texts, convert to list
if isinstance(texts, str):
texts = [texts]

try:
raw_embeddings = list(self.model.embed(texts))
except Exception as e:
raise VectorisationError(
"Failed to generate embeddings using FastEmbed.",
context={
"vectoriser": "fastembed",
"model": self.model_name,
"n_texts": len(texts),
"cause": str(e),
"cause_type": type(e).__name__,
},
) from e

try:
embeddings = np.asarray(raw_embeddings, dtype=np.float32)
except Exception as e:
raise VectorisationError(
"Failed to convert FastEmbed embeddings to a numpy array.",
context={
"vectoriser": "fastembed",
"model": self.model_name,
"n_texts": len(texts),
"cause": str(e),
"cause_type": type(e).__name__,
},
) from e

if embeddings.ndim == 1:
embeddings = embeddings.reshape(1, -1)

if embeddings.ndim != 2: # noqa: PLR2004
raise VectorisationError(
"FastEmbed returned embeddings with an unexpected shape.",
context={
"vectoriser": "fastembed",
"model": self.model_name,
"n_texts": len(texts),
"shape": list(embeddings.shape),
},
)

return embeddings

@staticmethod
def list_supported_models() -> list[dict[str, any]]:
"""Wrapper to list the supported models.

Returns:
list[dict[str, Any]]: A list of dictionaries containing the model information.
"""
check_deps(["fastembed"], extra="fastembed")
from fastembed import TextEmbedding # type: ignore

return TextEmbedding.list_supported_models()
Loading
Loading