Repository: JohnSnowLabs/langtest (language: python)

Issue:
Integrating error codes
# Description
<!-- Thoroughly explain what you are doing and why you are doing it -->

Introduced standardized exceptions to improve consistency and efficiency in error-handling processes. By implementing a uniform set of exceptions, you establish a structured approach to managing errors across your system. This not only streamlines the debugging process but also enhances code reliability. Developers can now easily identify and address issues, fostering a more resilient and maintainable codebase. 

-------------------------------------------------------------------------------------------------
➤ Fixes #854 

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Usage
<!-- If applicable: show a code snippet or a command on how to use this new addition -->

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [ ] I've added Google style docstrings to my code.
- [ ] I've used `pydantic` for typing when/where necessary.
- [ ] I have linted my code
- [ ] I have added tests to cover my changes.

## Screenshots (if appropriate):


Discussion / hints:

Fix patch:
diff --git a/poetry.lock b/poetry.lock
index 32099f288..a552b1fc0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -198,6 +198,27 @@ typing-extensions = ">=4"
 [package.extras]
 tz = ["python-dateutil"]
 
+[[package]]
+name = "anyio"
+version = "3.7.1"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
+    {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
+test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (<0.22)"]
+
 [[package]]
 name = "appnope"
 version = "0.1.3"
@@ -610,7 +631,6 @@ files = [
     {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"},
     {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"},
     {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"},
-    {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"},
     {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"},
     {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"},
     {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"},
@@ -619,7 +639,6 @@ files = [
     {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"},
     {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"},
     {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"},
-    {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"},
     {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"},
     {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"},
     {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"},
@@ -628,7 +647,6 @@ files = [
     {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"},
     {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"},
     {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"},
-    {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"},
     {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"},
     {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"},
     {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"},
@@ -637,7 +655,6 @@ files = [
     {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"},
     {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"},
     {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"},
-    {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"},
     {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"},
     {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"},
     {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"},
@@ -1277,7 +1294,6 @@ files = [
     {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"},
     {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"},
     {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"},
-    {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"},
     {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"},
     {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"},
     {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"},
@@ -1286,7 +1302,6 @@ files = [
     {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"},
     {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"},
     {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"},
-    {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"},
     {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"},
     {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"},
     {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"},
@@ -1316,7 +1331,6 @@ files = [
     {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"},
     {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"},
     {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"},
-    {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"},
     {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"},
     {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"},
     {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"},
@@ -1325,7 +1339,6 @@ files = [
     {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"},
     {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"},
     {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"},
-    {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"},
     {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"},
     {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"},
     {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"},
@@ -1628,6 +1641,31 @@ files = [
 [package.dependencies]
 attrs = ">=19.2.0"
 
+[[package]]
+name = "jsonpatch"
+version = "1.33"
+description = "Apply JSON-Patches (RFC 6902)"
+optional = true
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
+files = [
+    {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"},
+    {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"},
+]
+
+[package.dependencies]
+jsonpointer = ">=1.9"
+
+[[package]]
+name = "jsonpointer"
+version = "2.4"
+description = "Identify specific nodes in a JSON document (RFC 6901)"
+optional = true
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
+files = [
+    {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"},
+    {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"},
+]
+
 [[package]]
 name = "kiwisolver"
 version = "1.4.4"
@@ -1707,57 +1745,44 @@ files = [
 
 [[package]]
 name = "langchain"
-version = "0.0.200"
+version = "0.0.326"
 description = "Building applications with LLMs through composability"
 optional = true
 python-versions = ">=3.8.1,<4.0"
 files = [
-    {file = "langchain-0.0.200-py3-none-any.whl", hash = "sha256:f1567c52991e375ab1e41354587c54a931cf84e8e1a6427b380320825ec9390e"},
-    {file = "langchain-0.0.200.tar.gz", hash = "sha256:31c535deb45049d17aea3370de4ac5e21452ffb8b5e1a73a7ec477600b9e3b74"},
+    {file = "langchain-0.0.326-py3-none-any.whl", hash = "sha256:bee2a622d022d198a5c32831c54f4c7f925893608012b6863a3ff25591a8c620"},
+    {file = "langchain-0.0.326.tar.gz", hash = "sha256:09a86b0d2de000fb2158daa7b0fd8d12086dffaac2e6aed0dbb399c6280be781"},
 ]
 
 [package.dependencies]
 aiohttp = ">=3.8.3,<4.0.0"
+anyio = "<4.0"
 async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
-dataclasses-json = ">=0.5.7,<0.6.0"
-langchainplus-sdk = ">=0.0.9"
-numexpr = ">=2.8.4,<3.0.0"
+dataclasses-json = ">=0.5.7,<0.7"
+jsonpatch = ">=1.33,<2.0"
+langsmith = ">=0.0.52,<0.1.0"
 numpy = ">=1,<2"
-openapi-schema-pydantic = ">=1.2,<2.0"
-pydantic = ">=1,<2"
-PyYAML = ">=5.4.1"
+pydantic = ">=1,<3"
+PyYAML = ">=5.3"
 requests = ">=2,<3"
 SQLAlchemy = ">=1.4,<3"
 tenacity = ">=8.1.0,<9.0.0"
 
 [package.extras]
-all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.6,<0.3.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.2,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=3,<4)", "deeplake (>=3.3.0,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=2.8.6,<3.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.1.dev3,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "momento (>=1.5.0,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.1.2,<2.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.6.1,<0.7.0)", "spacy (>=3,<4)", "steamship (>=2.16.9,<3.0.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
-azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0a20230509004)", "openai (>=0,<1)"]
-cohere = ["cohere (>=3,<4)"]
+all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amadeus (>=8.1.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.9,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=4,<5)", "deeplake (>=3.6.8,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.6,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "libdeeplake (>=0.0.60,<0.0.61)", "librosa (>=0.10.0.post2,<0.11.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=1.2.4,<2.0.0)", "momento (>=1.10.1,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<4)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "python-arango (>=7.5.9,<8.0.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.3.1,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.6.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
+azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (>=0,<1)"]
+clarifai = ["clarifai (>=9.1.0)"]
+cli = ["typer (>=0.9.0,<0.10.0)"]
+cohere = ["cohere (>=4,<5)"]
 docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
 embeddings = ["sentence-transformers (>=2,<3)"]
-extended-testing = ["atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "chardet (>=5.1.0,<6.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "openai (>=0,<1)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "psychicapi (>=0.5,<0.6)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "telethon (>=1.28.5,<2.0.0)", "tqdm (>=4.48.0)", "zep-python (>=0.31)"]
-llms = ["anthropic (>=0.2.6,<0.3.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
-openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.4.0)"]
-qdrant = ["qdrant-client (>=1.1.2,<2.0.0)"]
+extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amazon-textract-caller (<2)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (>=0,<1)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
+javascript = ["esprima (>=4.0.1,<5.0.0)"]
+llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
+openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.6.0)"]
+qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"]
 text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
 
-[[package]]
-name = "langchainplus-sdk"
-version = "0.0.20"
-description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
-optional = true
-python-versions = ">=3.8.1,<4.0"
-files = [
-    {file = "langchainplus_sdk-0.0.20-py3-none-any.whl", hash = "sha256:07a869d476755803aa04c4986ce78d00c2fe4ff584c0eaa57d7570c9664188db"},
-    {file = "langchainplus_sdk-0.0.20.tar.gz", hash = "sha256:3d300e2e3290f68cc9d842c059f9458deba60e776c9e790309688cad1bfbb219"},
-]
-
-[package.dependencies]
-pydantic = ">=1,<2"
-requests = ">=2,<3"
-tenacity = ">=8.1.0,<9.0.0"
-
 [[package]]
 name = "langcodes"
 version = "3.3.0"
@@ -1772,6 +1797,21 @@ files = [
 [package.extras]
 data = ["language-data (>=1.1,<2.0)"]
 
+[[package]]
+name = "langsmith"
+version = "0.0.56"
+description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
+optional = true
+python-versions = ">=3.8.1,<4.0"
+files = [
+    {file = "langsmith-0.0.56-py3-none-any.whl", hash = "sha256:5aed1ad2395700442a6511651eca17d60eff56878f18bdd9e1d20b3c6f7e016c"},
+    {file = "langsmith-0.0.56.tar.gz", hash = "sha256:98382931f61a984a3d02cad07e4b986a0a7c843f87830172692c987deb8ba554"},
+]
+
+[package.dependencies]
+pydantic = ">=1,<3"
+requests = ">=2,<3"
+
 [[package]]
 name = "mako"
 version = "1.2.4"
@@ -2327,48 +2367,6 @@ files = [
 [package.dependencies]
 setuptools = "*"
 
-[[package]]
-name = "numexpr"
-version = "2.8.4"
-description = "Fast numerical expression evaluator for NumPy"
-optional = true
-python-versions = ">=3.7"
-files = [
-    {file = "numexpr-2.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a75967d46b6bd56455dd32da6285e5ffabe155d0ee61eef685bbfb8dafb2e484"},
-    {file = "numexpr-2.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db93cf1842f068247de631bfc8af20118bf1f9447cd929b531595a5e0efc9346"},
-    {file = "numexpr-2.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bca95f4473b444428061d4cda8e59ac564dc7dc6a1dea3015af9805c6bc2946"},
-    {file = "numexpr-2.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e34931089a6bafc77aaae21f37ad6594b98aa1085bb8b45d5b3cd038c3c17d9"},
-    {file = "numexpr-2.8.4-cp310-cp310-win32.whl", hash = "sha256:f3a920bfac2645017110b87ddbe364c9c7a742870a4d2f6120b8786c25dc6db3"},
-    {file = "numexpr-2.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:6931b1e9d4f629f43c14b21d44f3f77997298bea43790cfcdb4dd98804f90783"},
-    {file = "numexpr-2.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9400781553541f414f82eac056f2b4c965373650df9694286b9bd7e8d413f8d8"},
-    {file = "numexpr-2.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ee9db7598dd4001138b482342b96d78110dd77cefc051ec75af3295604dde6a"},
-    {file = "numexpr-2.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff5835e8af9a212e8480003d731aad1727aaea909926fd009e8ae6a1cba7f141"},
-    {file = "numexpr-2.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:655d84eb09adfee3c09ecf4a89a512225da153fdb7de13c447404b7d0523a9a7"},
-    {file = "numexpr-2.8.4-cp311-cp311-win32.whl", hash = "sha256:5538b30199bfc68886d2be18fcef3abd11d9271767a7a69ff3688defe782800a"},
-    {file = "numexpr-2.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:3f039321d1c17962c33079987b675fb251b273dbec0f51aac0934e932446ccc3"},
-    {file = "numexpr-2.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c867cc36cf815a3ec9122029874e00d8fbcef65035c4a5901e9b120dd5d626a2"},
-    {file = "numexpr-2.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:059546e8f6283ccdb47c683101a890844f667fa6d56258d48ae2ecf1b3875957"},
-    {file = "numexpr-2.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:845a6aa0ed3e2a53239b89c1ebfa8cf052d3cc6e053c72805e8153300078c0b1"},
-    {file = "numexpr-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:a38664e699526cb1687aefd9069e2b5b9387da7feac4545de446141f1ef86f46"},
-    {file = "numexpr-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eaec59e9bf70ff05615c34a8b8d6c7bd042bd9f55465d7b495ea5436f45319d0"},
-    {file = "numexpr-2.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b318541bf3d8326682ebada087ba0050549a16d8b3fa260dd2585d73a83d20a7"},
-    {file = "numexpr-2.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b076db98ca65eeaf9bd224576e3ac84c05e451c0bd85b13664b7e5f7b62e2c70"},
-    {file = "numexpr-2.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f12cc851240f7911a47c91aaf223dba753e98e46dff3017282e633602e76a7"},
-    {file = "numexpr-2.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c368aa35ae9b18840e78b05f929d3a7b3abccdba9630a878c7db74ca2368339"},
-    {file = "numexpr-2.8.4-cp38-cp38-win32.whl", hash = "sha256:b96334fc1748e9ec4f93d5fadb1044089d73fb08208fdb8382ed77c893f0be01"},
-    {file = "numexpr-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:a6d2d7740ae83ba5f3531e83afc4b626daa71df1ef903970947903345c37bd03"},
-    {file = "numexpr-2.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:77898fdf3da6bb96aa8a4759a8231d763a75d848b2f2e5c5279dad0b243c8dfe"},
-    {file = "numexpr-2.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df35324666b693f13a016bc7957de7cc4d8801b746b81060b671bf78a52b9037"},
-    {file = "numexpr-2.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ac9cfe6d0078c5fc06ba1c1bbd20b8783f28c6f475bbabd3cad53683075cab"},
-    {file = "numexpr-2.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df3a1f6b24214a1ab826e9c1c99edf1686c8e307547a9aef33910d586f626d01"},
-    {file = "numexpr-2.8.4-cp39-cp39-win32.whl", hash = "sha256:7d71add384adc9119568d7e9ffa8a35b195decae81e0abf54a2b7779852f0637"},
-    {file = "numexpr-2.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:9f096d707290a6a00b6ffdaf581ee37331109fb7b6c8744e9ded7c779a48e517"},
-    {file = "numexpr-2.8.4.tar.gz", hash = "sha256:d5432537418d18691b9115d615d6daa17ee8275baef3edf1afbbf8bc69806147"},
-]
-
-[package.dependencies]
-numpy = ">=1.13.3"
-
 [[package]]
 name = "numpy"
 version = "1.24.4"
@@ -2444,20 +2442,6 @@ dev = ["black (>=21.6b0,<22.0)", "pytest (==6.*)", "pytest-asyncio", "pytest-moc
 embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"]
 wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"]
 
-[[package]]
-name = "openapi-schema-pydantic"
-version = "1.2.4"
-description = "OpenAPI (v3) specification schema as pydantic class"
-optional = true
-python-versions = ">=3.6.1"
-files = [
-    {file = "openapi-schema-pydantic-1.2.4.tar.gz", hash = "sha256:3e22cf58b74a69f752cc7e5f1537f6e44164282db2700cbbcd3bb99ddd065196"},
-    {file = "openapi_schema_pydantic-1.2.4-py3-none-any.whl", hash = "sha256:a932ecc5dcbb308950282088956e94dea069c9823c84e507d64f6b622222098c"},
-]
-
-[package.dependencies]
-pydantic = ">=1.8.2"
-
 [[package]]
 name = "packaging"
 version = "23.1"
@@ -3602,6 +3586,17 @@ files = [
     {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
 ]
 
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+description = "Sniff out which async library your code is running under"
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+    {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
+
 [[package]]
 name = "snowballstemmer"
 version = "2.2.0"
@@ -4662,4 +4657,4 @@ transformers = ["accelerate", "datasets", "torch", "transformers"]
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.8.1,<4.0"
-content-hash = "73861cbd97b814fdb52f3d74f3fa893026c580bbc163abc1267f7822d196681f"
+content-hash = "518ff773abd5a13ba13a2b4d20448699f38b9aeed2fc62183a2958f5e6cbc0f6"
diff --git a/pyproject.toml b/pyproject.toml
index c4db2521e..a15173644 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,10 +41,10 @@ per-file-ignores = '__init__.py:F401'
 [tool.black]
 line-length = 90
 extend-exclude = "langtest/transform/constants.py"
+exclude = 'langtest/errors.py'
 
 [tool.poetry.dependencies]
 python = ">=3.8.1,<4.0"
-langchain = { version = "^0.0.200", optional = true }
 pydantic = "1.10.6"
 johnsnowlabs = { version = "4.3.5", optional = true }
 rouge-score = { version = "^0.1.2", optional = true }
@@ -69,6 +69,7 @@ mlflow = {version = "^2.5.0", optional = true}
 datasets = {version = ">=2.14.0", optional = true}
 matplotlib = {version = "^3.7.2", optional = true}
 tenacity = {version = "^8.2.2", optional = true}
+langchain = {version = "0.0.326", optional = true}
 
 [tool.poetry.extras]
 transformers = ["transformers", "torch", "accelerate", "datasets"]
@@ -101,7 +102,7 @@ lint = "pflake8 langtest/"
 format = "black langtest/ tests/"
 check-docstrings = "pydocstyle langtest/ --add-select=D417 --add-ignore=D100,D104,D105,D400,D415 --convention=google"
 is-formatted = "black --check langtest/ tests/"
-force-cpu-torch = "python -m pip install torch==2.0.0 --index-url https://download.pytorch.org/whl/cpu"
+force-cpu-torch = "python -m pip install torch --index-url https://download.pytorch.org/whl/cpu"
 
 
 [build-system]


Test patch:
diff --git a/langtest/__init__.py b/langtest/__init__.py
index e6c46fee8..0aff4773a 100644
--- a/langtest/__init__.py
+++ b/langtest/__init__.py
@@ -1,5 +1,5 @@
 from .datahandler.datasource import DataFactory
-from .modelhandler.modelhandler import ModelFactory
+from .modelhandler.modelhandler import ModelAPI
 from .langtest import Harness
 
 """
@@ -12,13 +12,13 @@
 workflow: generate --> run --> report --> save
 >>> from langtest import Harness
 
-ModelFactory is for handling of strings to models
+ModelAPI is for handling of strings to models
 like access or download resources from cloud.
->>> from langtest import ModelFactory
+>>> from langtest import ModelAPI
 
 DataFactory is for handling of like csv, json, conll ...
 >>> from langtest import DataFactory
 
 
 """
-__all__ = [Harness, ModelFactory, DataFactory]
+__all__ = [Harness, ModelAPI, DataFactory]
diff --git a/langtest/augmentation/__init__.py b/langtest/augmentation/__init__.py
index af3f156f8..e6ea3ad23 100644
--- a/langtest/augmentation/__init__.py
+++ b/langtest/augmentation/__init__.py
@@ -17,6 +17,7 @@
 from langtest.utils.custom_types.output import NEROutput
 from langtest.utils.custom_types.predictions import NERPrediction, SequenceLabel
 from langtest.utils.custom_types.sample import NERSample
+from langtest.tasks import TaskManager
 
 
 class BaseAugmentaion(ABC):
@@ -62,7 +63,7 @@ class AugmentRobustness(BaseAugmentaion):
 
     def __init__(
         self,
-        task: str,
+        task: TaskManager,
         h_report: "pd.DataFrame",
         config: Dict,
         custom_proportions: Union[Dict, List] = None,
@@ -114,17 +115,17 @@ def fix(
             List[Dict[str, Any]]: A list of augmented data samples.
         """
 
-        if "source" in training_data and training_data["source"] == "huggingface":
-            self.df = HuggingFaceDataset(training_data["data_source"], self.task)
-            data = self.df.load_data(
-                feature_column=training_data.get("feature_column", "text"),
-                target_column=training_data.get("target_column", "label"),
-                split=training_data.get("split", "test"),
-                subset=training_data.get("subset", None),
-            )
-        else:
-            self.df = DataFactory(training_data, self.task)
-            data = self.df.load()
+        # if "source" in training_data and training_data["source"] == "huggingface":
+        #     self.df = HuggingFaceDataset(training_data, self.task)
+        #     data = self.df.load_data(
+        #         feature_column=training_data.get("feature_column", "text"),
+        #         target_column=training_data.get("target_column", "label"),
+        #         split=training_data.get("split", "test"),
+        #         subset=training_data.get("subset", None),
+        #     )
+        # else:
+        self.df = DataFactory(training_data, self.task)
+        data = self.df.load()
         TestFactory.is_augment = True
         supported_tests = TestFactory.test_scenarios()
         suggest: pd.DataFrame = self.suggestions(self.h_report)
@@ -185,12 +186,12 @@ def fix(
         if "." not in training_data["data_source"]:
             if export_mode == "inplace":
                 final_aug_data = list(hash_map.values())
-                self.df.export_data(final_aug_data, output_path)
+                self.df.export(final_aug_data, output_path)
             elif export_mode == "transformed":
-                self.df.export_data(transformed_data, output_path)
+                self.df.export(transformed_data, output_path)
             else:
                 data.extend(final_aug_data)
-                self.df.export_data(data, output_path)
+                self.df.export(data, output_path)
 
             TestFactory.is_augment = False
             return final_aug_data
@@ -309,7 +310,7 @@ class TemplaticAugment(BaseAugmentaion):
             Performs the templatic augmentation and exports the results to a specified path.
     """
 
-    def __init__(self, templates: Union[str, List[str]], task: str) -> None:
+    def __init__(self, templates: Union[str, List[str]], task: TaskManager) -> None:
         """This constructor for the TemplaticAugment class.
 
         Args:
@@ -352,7 +353,13 @@ def fix(
         """
         df = DataFactory(training_data, self.__task)
         data = df.load()
-        new_data = data.copy() if append_original else []
+        new_data = (
+            data.copy()
+            if isinstance(data, (pd.DataFrame, pd.Series))
+            else copy.deepcopy(data)
+            if append_original
+            else []
+        )
         self.__search_results = self.search_sample_results(data)
         if not max_num:
             max_num = max(len(i) for i in self.__search_results.values())
diff --git a/langtest/datahandler/datasource.py b/langtest/datahandler/datasource.py
index 4e32b2b99..c54ebe06e 100644
--- a/langtest/datahandler/datasource.py
+++ b/langtest/datahandler/datasource.py
@@ -2,8 +2,8 @@
 import importlib
 import logging
 import os
-import re
 import random
+import re
 from abc import ABC, abstractmethod
 from collections import defaultdict
 from typing import Dict, List, Union
@@ -11,8 +11,9 @@
 
 import jsonlines
 import pandas as pd
+from langtest.tasks.task import TaskManager
+from langtest.transform.constants import DATASETS
 
-from langtest.utils.custom_types import sample
 from .format import Formatter
 from langtest.utils.custom_types import (
     NEROutput,
@@ -24,21 +25,11 @@
     SequenceClassificationSample,
     SequenceLabel,
     SummarizationSample,
-    ToxicitySample,
-    TranslationSample,
-    ClinicalSample,
-    SecuritySample,
-    DisinformationSample,
-    SensitivitySample,
-    WinoBiasSample,
-    LegalSample,
-    FactualitySample,
-    SycophancySample,
     CrowsPairsSample,
-    StereoSetSample,
+    SycophancySample,
 )
 from ..utils.lib_manager import try_import_lib
-from ..transform.constants import DATASETS
+from ..errors import Warnings, Errors
 
 COLUMN_MAPPER = {
     "text-classification": {
@@ -109,12 +100,14 @@
 }
 
 
-class _IDataset(ABC):
+class BaseDataset(ABC):
     """Abstract base class for Dataset.
 
     Defines the load_data method that all subclasses must implement.
     """
 
+    data_sources = defaultdict()
+
     @abstractmethod
     def load_raw_data(self):
         """Load data from the file_path into raw format."""
@@ -137,6 +130,12 @@ def export_data(self, data: List[Sample], output_path: str):
         """
         return NotImplementedError()
 
+    @classmethod
+    def __init_subclass__(cls, **kwargs):
+        super().__init_subclass__(**kwargs)
+        dataset_cls = cls.__name__.replace("Dataset", "").lower()
+        cls.data_sources[dataset_cls] = cls
+
 
 class DataFactory:
     """Data factory for creating Dataset objects.
@@ -145,7 +144,9 @@ class DataFactory:
     correct Dataset type based on the file extension.
     """
 
-    def __init__(self, file_path: dict, task: str, **kwargs) -> None:
+    data_sources: Dict[str, BaseDataset] = BaseDataset.data_sources
+
+    def __init__(self, file_path: dict, task: TaskManager, **kwargs) -> None:
         """Initializes DataFactory object.
 
         Args:
@@ -153,31 +154,35 @@ def __init__(self, file_path: dict, task: str, **kwargs) -> None:
             task (str): Task to be evaluated.
         """
         if not isinstance(file_path, dict):
-            raise ValueError("'file_path' must be a dictionary.")
+            raise ValueError(Errors.E024)
 
         if "data_source" not in file_path:
-            raise ValueError(
-                "The 'data_source' key must be provided in the 'file_path' dictionary."
-            )
+            raise ValueError(Errors.E025)
         self._custom_label = file_path
         self._file_path = file_path.get("data_source")
-        self._class_map = {
-            cls.__name__.replace("Dataset", "").lower(): cls
-            for cls in _IDataset.__subclasses__()
-        }
-        _, self.file_ext = os.path.splitext(self._file_path)
-        if len(self.file_ext) > 0:
-            self.file_ext = self.file_ext.replace(".", "")
-        else:
-            self._file_path = self._load_dataset(self._file_path)
+
+        if isinstance(self._file_path, str):
             _, self.file_ext = os.path.splitext(self._file_path)
+
+            if len(self.file_ext) > 0:
+                self.file_ext = self.file_ext.replace(".", "")
+            elif "source" in file_path:
+                self.file_ext = file_path["source"]
+                self._file_path = file_path
+            elif self._file_path in ("synthetic-math-data", "synthetic-nlp-data"):
+                self.file_ext = "syntetic"
+                self._file_path = file_path
+            else:
+                self._file_path = self._load_dataset(self._file_path)
+                _, self.file_ext = os.path.splitext(self._file_path)
+
         self.task = task
-        self.init_cls = None
+        self.init_cls: BaseDataset = None
         self.kwargs = kwargs
 
     def load_raw(self):
         """Loads the data into a raw format"""
-        self.init_cls = self._class_map[self.file_ext.replace(".", "")](
+        self.init_cls = self.data_sources[self.file_ext.replace(".", "")](
             self._file_path, task=self.task, **self.kwargs
         )
         return self.init_cls.load_raw_data()
@@ -188,12 +193,19 @@ def load(self) -> List[Sample]:
         Returns:
             list[Sample]: Loaded text data.
         """
+        # if "data_source" in self._file_path:
+        #     if isinstance(self._file_path["data_source"], list):
+        #         return self._file_path
+        # elif isinstance(self._file_path, list):
+        #     return self._file_path
+
         if len(self._custom_label) > 1 and self.file_ext == "csv":
-            self.init_cls = self._class_map[self.file_ext.replace(".", "")](
+            self.init_cls = self.data_sources[self.file_ext.replace(".", "")](
                 self._custom_label, task=self.task, **self.kwargs
             )
+        # if
         else:
-            self.init_cls = self._class_map[self.file_ext.replace(".", "")](
+            self.init_cls = self.data_sources[self.file_ext.replace(".", "")](
                 self._file_path, task=self.task, **self.kwargs
             )
         return self.init_cls.load_data()
@@ -265,13 +277,15 @@ def filter_curated_bias(
             List[Sample]: list of processed samples
         """
         data = []
-        warning_message = ""
         for item in bias_data:
             if item.test_type in tests_to_filter:
                 data.append(item)
-
-        warning_message += f"Filtering provided bias tests from {len(bias_data)} samples - {len(bias_data) - len(data)} samples removed "
-        logging.warning(warning_message)
+        logging.warning(
+            Warnings.W003.format(
+                len_bias_data=len(bias_data),
+                len_samples_removed=len(bias_data) - len(data),
+            )
+        )
         return data
 
     @classmethod
@@ -385,14 +399,14 @@ def _load_dataset(cls, dataset_name: str) -> str:
         return datasets_info[dataset_name]
 
 
-class ConllDataset(_IDataset):
-    """Class to handle Conll files. Subclass of _IDataset."""
+class ConllDataset(BaseDataset):
+    """Class to handle Conll files. Subclass of BaseDataset."""
 
     supported_tasks = ["ner"]
 
     COLUMN_NAMES = {task: COLUMN_MAPPER[task] for task in supported_tasks}
 
-    def __init__(self, file_path: str, task: str) -> None:
+    def __init__(self, file_path: str, task: TaskManager) -> None:
         """Initializes ConllDataset object.
 
         Args:
@@ -402,10 +416,6 @@ def __init__(self, file_path: str, task: str) -> None:
         super().__init__()
         self._file_path = file_path
 
-        if task != "ner":
-            raise ValueError(
-                f"Given task ({task}) is not matched with ner. CoNLL dataset can ne only loaded for ner!"
-            )
         self.task = task
 
     def load_raw_data(self) -> List[Dict]:
@@ -437,9 +447,7 @@ def load_raw_data(self) -> List[Dict]:
                     valid_tokens, token_list = self.__token_validation(tokens)
 
                     if not valid_tokens:
-                        logging.warning(
-                            f"\n{'='*100}\nInvalid tokens found in sentence:\n{sent}. \nSkipping sentence.\n{'='*100}\n"
-                        )
+                        logging.warning(Warnings.W004.format(sent=sent))
                         continue
 
                     #  get token and labels from the split
@@ -481,9 +489,7 @@ def load_data(self) -> List[NERSample]:
                     valid_tokens, token_list = self.__token_validation(tokens)
 
                     if not valid_tokens:
-                        logging.warning(
-                            f"\n{'='*100}\nInvalid tokens found in sentence:\n{sent}. \nSkipping sentence.\n{'='*100}\n"
-                        )
+                        logging.warning(Warnings.W004.format(sent=sent))
                         continue
 
                     #  get token and labels from the split
@@ -510,7 +516,7 @@ def load_data(self) -> List[NERSample]:
                     original = " ".join([label.span.word for label in ner_labels])
 
                     data.append(
-                        NERSample(
+                        self.task.get_sample_class(
                             original=original,
                             expected_results=NEROutput(predictions=ner_labels),
                         )
@@ -557,10 +563,7 @@ def __token_validation(self, tokens: str) -> (bool, List[List[str]]):
                 token_list.append(tsplit)
                 valid_labels.append(tsplit[-1])
             else:
-                logging.warning(
-                    # invalid label entries in the sentence
-                    f" Invalid or Missing label entries in the sentence: {t}"
-                )
+                logging.warning(Warnings.W008.format(sent=t))
                 return False, token_list
 
         if valid_labels[0].startswith("I-"):
@@ -574,8 +577,8 @@ def __token_validation(self, tokens: str) -> (bool, List[List[str]]):
         return True, token_list  # All labels are valid
 
 
-class JSONDataset(_IDataset):
-    """Class to handle JSON dataset files. Subclass of _IDataset."""
+class JSONDataset(BaseDataset):
+    """Class to handle JSON dataset files. Subclass of BaseDataset."""
 
     def __init__(self, file_path: str):
         """Initializes JSONDataset object.
@@ -610,7 +613,7 @@ def export_data(self, data: List[Sample], output_path: str):
         raise NotImplementedError()
 
 
-class CSVDataset(_IDataset):
+class CSVDataset(BaseDataset):
     supported_tasks = [
         "ner",
         "text-classification",
@@ -621,7 +624,7 @@ class CSVDataset(_IDataset):
     COLUMN_NAMES = {task: COLUMN_MAPPER[task] for task in supported_tasks}
 
     """
-    A class to handle CSV files datasets. Subclass of _IDataset.
+    A class to handle CSV files datasets. Subclass of BaseDataset.
 
     Attributes:
         _file_path (Union[str, Dict]):
@@ -633,7 +636,7 @@ class CSVDataset(_IDataset):
             The delimiter used in the CSV file to separate columns (only for file_path as str).
     """
 
-    def __init__(self, file_path: Union[str, Dict], task: str, **kwargs) -> None:
+    def __init__(self, file_path: Union[str, Dict], task: TaskManager, **kwargs) -> None:
         """
         Initializes a CustomCSVDataset object.
 
@@ -658,13 +661,10 @@ def __init__(self, file_path: Union[str, Dict], task: str, **kwargs) -> None:
         if type(file_path) == dict:
             self.delimiter = self._find_delimiter(file_path["data_source"])
         else:
-            if task in self.COLUMN_NAMES:
+            if task.task_name in self.COLUMN_NAMES:
                 self.COLUMN_NAMES = self.COLUMN_NAMES[self.task]
             elif "is_import" not in kwargs:
-                raise ValueError(
-                    f"Given task ({task}) is not matched with template. \
-                    CSV dataset can ne only loaded for text-classification and ner!"
-                )
+                raise ValueError(Errors.E026.format(task=task))
             self.delimiter = self._find_delimiter(file_path)
 
         self.column_map = None
@@ -693,7 +693,9 @@ def load_raw_data(self, standardize_columns: bool = False) -> List[Dict]:
 
             if feature_column not in df.columns or target_column not in df.columns:
                 raise ValueError(
-                    f"Columns '{feature_column}' and '{target_column}' not found in the dataset."
+                    Errors.E027.format(
+                        feature_column=feature_column, target_column=target_column
+                    )
                 )
 
             if self.task == "text-classification":
@@ -774,25 +776,36 @@ def load_data(self) -> List[Sample]:
             return self._import_data(self._file_path, **kwargs)
 
         if type(self._file_path) == dict:
-            dataset = pd.read_csv(self._file_path["data_source"])
+            dataset = pd.read_csv(
+                self._file_path["data_source"], encoding_errors="ignore"
+            )
         else:
-            dataset = pd.read_csv(self._file_path)
+            dataset = pd.read_csv(self._file_path, encoding_errors="ignore")
             if not self.column_map:
                 self.column_map = self._match_column_names(list(dataset.columns))
 
-        task_functions = {
-            "text-classification": self.load_data_classification,
-            "ner": self.load_data_ner,
-            "summarization": self.load_data_summarization,
-            "question-answering": self.load_data_question_answering,
-            "crows-pairs": self.load_data_crows_pairs,
-        }
+        data = []
+        column_names = self._file_path
 
-        if self.task in task_functions:
-            task_function = task_functions[self.task]
-            return task_function(dataset)
+        # remove the data_source key from the column_names dict
+        if isinstance(column_names, dict):
+            column_names.pop("data_source")
         else:
-            raise ValueError(f"Unsupported task: {self.task}")
+            column_names = dict()
+
+        for idx, row_data in enumerate(dataset.to_dict(orient="records")):
+            try:
+                sample = self.task.create_sample(
+                    row_data,
+                    **column_names,
+                )
+                data.append(sample)
+
+            except Exception as e:
+                logging.warning(Warnings.W005.format(idx=idx, row_data=row_data, e=e))
+                continue
+
+        return data
 
     def export_data(self, data: List[Sample], output_path: str):
         """Exports the data to the corresponding format and saves it to 'output_path'.
@@ -868,13 +881,12 @@ def load_data_ner(
             feature_column = self._file_path.get("feature_column", "text")
             target_column = self._file_path.get("target_column", "ner")
 
-            if (
-                feature_column not in dataset.columns
-                or target_column not in dataset.columns
-            ):
-                raise ValueError(
-                    f"Columns '{feature_column}' and '{target_column}' not found in the dataset."
-                )
+            if feature_column not in dataset.columns:
+                raise ValueError(Errors.E027.format(feature_column=feature_column))
+
+            if target_column not in dataset.columns:
+                logging.warning(Warnings.W006.format(target_column=target_column))
+                dataset["ner"] = None
 
             dataset.rename(
                 columns={feature_column: "text", target_column: "ner"},
@@ -913,13 +925,12 @@ def load_data_classification(
             feature_column = self._file_path.get("feature_column", "text")
             target_column = self._file_path.get("target_column", "label")
 
-            if (
-                feature_column not in dataset.columns
-                or target_column not in dataset.columns
-            ):
-                raise ValueError(
-                    f"Columns '{feature_column}' and '{target_column}' not found in the dataset."
-                )
+            if feature_column not in dataset.columns:
+                raise ValueError(Errors.E027.format(feature_column=feature_column))
+
+            if target_column not in dataset.columns:
+                logging.warning(Warnings.W006.format(target_column=target_column))
+                dataset["label"] = None
 
             if feature_column and target_column:
                 dataset.rename(
@@ -958,13 +969,9 @@ def load_data_summarization(
             target_column = self._file_path.get("target_column", "summary")
 
             if feature_column not in dataset.columns:
-                raise ValueError(
-                    f"feature_column '{feature_column}' not found in the dataset."
-                )
+                raise ValueError(Errors.E027.format(feature_column=feature_column))
             if target_column not in dataset.columns:
-                logging.warning(
-                    f"target_column '{target_column}' not found in the dataset."
-                )
+                logging.warning(Warnings.W006.format(target_column=target_column))
                 dataset["summary"] = None
             else:
                 dataset.rename(columns={target_column: "summary"}, inplace=True)
@@ -1016,22 +1023,18 @@ def load_data_question_answering(
                 or feature_column["question"] not in dataset_columns
             ):
                 raise ValueError(
-                    f"'feature_column' '{feature_column['question']}' not found in the dataset."
+                    Errors.E027.format(feature_column=feature_column["question"])
                 )
 
             if target_column not in dataset_columns:
-                logging.warning(
-                    f"target_column '{target_column}' not found in the dataset."
-                )
+                logging.warning(Warnings.W006.format(target_column=target_column))
                 dataset["answer"] = None
             else:
                 dataset.rename(columns={target_column: "answer"}, inplace=True)
 
             if passage_column:
                 if passage_column not in dataset_columns:
-                    logging.warning(
-                        f"'feature_column' '{passage_column}' not found in the dataset."
-                    )
+                    logging.warning(Warnings.W007.format(passage_column=passage_column))
                     dataset["passage"] = "-"
                 else:
                     dataset.rename(columns={passage_column: "passage"}, inplace=True)
@@ -1223,9 +1226,10 @@ def _match_column_names(self, column_names: List[str]) -> Dict[str, str]:
             "ner" in not_referenced_columns or "label" in not_referenced_columns
         ):
             raise OSError(
-                f"CSV file is invalid. CSV handler works with template column names!\n"
-                f"{', '.join(not_referenced_columns.keys())} column could not be found in header.\n"
-                f"You can use following namespaces:\n{not_referenced_columns}"
+                Errors.E028.__format__(
+                    not_referenced_columns_keys=", ".join(not_referenced_columns.keys()),
+                    not_referenced_columns=not_referenced_columns,
+                )
             )
         return column_map
 
@@ -1240,26 +1244,21 @@ def _import_data(self, file_name, **kwargs) -> List[Sample]:
             List[Sample]:       list of samples
         """
         data = pd.read_csv(file_name, **kwargs)
-        custom_names = {
-            "question-answering": "qa",
-            "text-classification": "sequenceclassification",
-        }
-        sample_models = {
-            k.lower(): v for k, v in sample.__dict__.items() if k.endswith("Sample")
-        }
         samples = []
 
         for i in data.to_dict(orient="records"):
-            if self.task in custom_names:
-                sample_name = custom_names[self.task] + "sample"
-            else:
-                sample_name = self.task.lower() + "sample"
-            samples.append(sample_models[sample_name](**i))
+            # if self.task in custom_names:
+            #     sample_name = custom_names[self.task] + "sample"
+            # else:
+            #     sample_name = self.task.lower() + "sample"
+            # samples.append(sample_models[sample_name](**i))
+            sample = self.task.get_sample_class(**i)
+            samples.append(sample)
         return samples
 
 
-class JSONLDataset(_IDataset):
-    """Class to handle JSONL datasets. Subclass of _IDataset."""
+class JSONLDataset(BaseDataset):
+    """Class to handle JSONL datasets. Subclass of BaseDataset."""
 
     supported_tasks = [
         "ner",
@@ -1279,7 +1278,7 @@ class JSONLDataset(_IDataset):
     ]
     COLUMN_NAMES = {task: COLUMN_MAPPER[task] for task in supported_tasks}
 
-    def __init__(self, file_path: str, task: str) -> None:
+    def __init__(self, file_path: str, task: TaskManager) -> None:
         """Initializes JSONLDataset object.
 
         Args:
@@ -1304,18 +1303,20 @@ def _match_column_names(self, column_names: List[str]) -> Dict[str, str]:
         """
         column_map = {}
         for column in column_names:
-            for key, reference_columns in self.COLUMN_NAMES[self.task].items():
+            for key, reference_columns in self.COLUMN_NAMES[self.task.task_name].items():
                 if column.lower() in reference_columns:
                     column_map[key] = column
 
         not_referenced_columns = [
-            col for col in self.COLUMN_NAMES[self.task] if col not in column_map
+            col for col in self.COLUMN_NAMES[self.task.task_name] if col not in column_map
         ]
 
         if "text" in not_referenced_columns:
             raise OSError(
-                f"Your dataset needs to have at least have a column with one of the following name: "
-                f"{self.COLUMN_NAMES[self.task]['text']}, found: {column_names}."
+                Errors.E029.format(
+                    valid_column_names=self.COLUMN_NAMES[self.task.task_name]["text"],
+                    column_names=column_names,
+                )
             )
 
         for missing_col in not_referenced_columns:
@@ -1328,7 +1329,7 @@ def load_raw_data(self) -> List[Dict]:
             data = [obj for obj in reader]
         return data
 
-    def load_data(self) -> List[Sample]:
+    def load_data(self, *args, **kwargs) -> List[Sample]:
         """Loads data from a JSONL file and format it into a list of Sample.
 
         Returns:
@@ -1337,140 +1338,24 @@ def load_data(self) -> List[Sample]:
         data = []
         with jsonlines.open(self._file_path) as reader:
             for item in reader:
-                if self.column_matcher is None:
-                    self.column_matcher = self._match_column_names(item.keys())
-
-                if self.task == "question-answering":
-                    expected_results = item.get(self.column_matcher["answer"])
-                    if isinstance(expected_results, str) or isinstance(
-                        expected_results, bool
-                    ):
-                        expected_results = [str(expected_results)]
-
-                    data.append(
-                        QASample(
-                            original_question=item[self.column_matcher["text"]],
-                            original_context=item.get(
-                                self.column_matcher["context"], "-"
-                            ),
-                            expected_results=expected_results,
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-
-                elif self.task == "summarization":
-                    expected_results = item.get(self.column_matcher["summary"])
-                    if isinstance(expected_results, str) or isinstance(
-                        expected_results, bool
-                    ):
-                        expected_results = [str(expected_results)]
-                    data.append(
-                        SummarizationSample(
-                            original=item[self.column_matcher["text"]],
-                            expected_results=expected_results,
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-                elif self.task == "toxicity":
-                    data.append(
-                        ToxicitySample(
-                            prompt=item[self.column_matcher["text"]],
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-
-                elif self.task == "translation":
-                    data.append(
-                        TranslationSample(
-                            original=item[self.column_matcher["text"]],
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-                elif self.task == "security":
-                    data.append(
-                        SecuritySample(
-                            prompt=item["text"],
-                            task=self.task,
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-
-                elif self.task == "clinical-tests":
-                    data.append(
-                        ClinicalSample(
-                            patient_info_A=item["Patient info A"],
-                            patient_info_B=item["Patient info B"],
-                            diagnosis=item["Diagnosis"],
-                            task=self.task,
-                            dataset_name=self._file_path.split("/")[-2],
-                            clinical_domain=item["clinical_domain"],
-                        )
-                    )
-                elif self.task == "disinformation-test":
-                    data.append(
-                        DisinformationSample(
-                            hypothesis=item["hypothesis"],
-                            statements=item["statements"],
-                            task=self.task,
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    ),
-                elif self.task == "sensitivity-test":
-                    supported_data = ("NQ-open", "OpenBookQA", "wikiDataset")
-                    if self._file_path.split("/")[-2] in supported_data:
-                        data.append(
-                            SensitivitySample(original=item[self.column_matcher["text"]])
-                        )
-                    else:
-                        raise ValueError(
-                            f"Unsupported dataset for sensitivity-test. Please use one of: {', '.join(supported_data)} with their 'test' or 'test-tiny' version."
-                        )
-
-                elif self.task == "wino-bias":
-                    data.append(
-                        WinoBiasSample(
-                            masked_text=item["text"],
-                            options=item["options"],
-                            task=self.task,
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-
-                elif self.task == "legal-tests":
-                    data.append(
-                        LegalSample(
-                            case=item["case"],
-                            legal_claim=item["legal-claim"],
-                            legal_conclusion_A=item["legal_conclusion_a"],
-                            legal_conclusion_B=item["legal_conclusion_b"],
-                            correct_conlusion=item["correct_choice"],
-                            task=self.task,
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-
-                elif self.task == "factuality-test":
-                    data.append(
-                        FactualitySample(
-                            article_sent=item["article_sent"],
-                            incorrect_sent=item["incorrect_sent"],
-                            correct_sent=item["correct_sent"],
-                            dataset_name=self._file_path.split("/")[-2],
-                        )
-                    )
-
-                elif self.task == "stereoset":
-                    data.append(
-                        StereoSetSample(
-                            test_type=item["type"],
-                            target=item["target"],
-                            bias_type=item["bias_type"],
-                            context=item["context"],
-                            sent_stereo=item["stereotype"],
-                            sent_antistereo=item["anti-stereotype"],
-                            sent_unrelated=item["unrelated"],
-                        )
-                    )
+                dataset_name = self._file_path.split("/")[-2]
+                sample = self.task.create_sample(
+                    item, dataset_name=dataset_name, *args, **kwargs
+                )
+                data.append(sample)
+
+                # elif self.task == "stereoset":
+                #     data.append(
+                #         StereoSetSample(
+                #             test_type=item["type"],
+                #             target=item["target"],
+                #             bias_type=item["bias_type"],
+                #             context=item["context"],
+                #             sent_stereo=item["stereotype"],
+                #             sent_antistereo=item["anti-stereotype"],
+                #             sent_unrelated=item["unrelated"],
+                #         )
+                #     )
 
         return data
 
@@ -1486,7 +1371,7 @@ def export_data(self, data: List[Sample], output_path: str):
         raise NotImplementedError()
 
 
-class HuggingFaceDataset(_IDataset):
+class HuggingFaceDataset(BaseDataset):
     """Example dataset class that loads data using the Hugging Face dataset library."""
 
     supported_tasks = [
@@ -1499,17 +1384,19 @@ class HuggingFaceDataset(_IDataset):
     LIB_NAME = "datasets"
     COLUMN_NAMES = {task: COLUMN_MAPPER[task] for task in supported_tasks}
 
-    def __init__(self, dataset_name: str, task: str):
+    def __init__(self, source_info: dict, task: TaskManager, **kwargs):
         """Initialize the HuggingFaceDataset class.
 
         Args:
-            dataset_name (str):
+            source_info (dict):
                 Name of the dataset to load.
             task (str):
                 Task to be evaluated on.
         """
-        self.dataset_name = dataset_name
+        self.source_info = source_info
+        self.dataset_name = source_info["data_source"]
         self.task = task
+        self.kwargs = kwargs
         self._check_datasets_package()
 
     def _check_datasets_package(self):
@@ -1521,210 +1408,7 @@ def _check_datasets_package(self):
             dataset_module = importlib.import_module(self.LIB_NAME)
             self.load_dataset = getattr(dataset_module, "load_dataset")
         else:
-            raise ModuleNotFoundError(
-                f"The '{self.LIB_NAME}' package is not installed. Please install it using 'pip install {self.LIB_NAME}'."
-            )
-
-    def load_data_ner(
-        self,
-        feature_column: str,
-        target_column: str,
-        split: str,
-        subset: str = None,
-    ) -> List[Sample]:
-        """Load the specified split from the given ner dataset."""
-        feature_column = "text" if feature_column is None else feature_column
-        target_column = "label" if target_column is None else target_column
-        split = "test" if split is None else split
-
-        if subset:
-            dataset = self.load_dataset(self.dataset_name, name=subset, split=split)
-        else:
-            dataset = self.load_dataset(self.dataset_name, split=split)
-
-        if "label" in str(type(dataset.features[target_column].feature)):
-            label_names = dataset.features[target_column].feature.names
-            dataset = map(
-                lambda example: {
-                    "tokens": example[feature_column],
-                    "ner_tags": [label_names[x] for x in example[target_column]],
-                },
-                dataset,
-            )
-        else:
-            dataset = map(
-                lambda example: {
-                    "tokens": example[feature_column],
-                    "ner_tags": example[target_column],
-                },
-                dataset,
-            )
-
-        samples = [self._row_to_ner_sample(example) for example in dataset]
-        return samples
-
-    def load_data_classification(
-        self,
-        feature_column: str,
-        target_column: str,
-        split: str,
-        subset: str = None,
-    ) -> List[Sample]:
-        """Load the specified split from the dataset library.
-
-        Args:
-            feature_column (str):
-                Name of the feature_column column.
-            target_column (str):
-                Name of the target_column column.
-            split (str):
-                Name of the split to load (e.g., train, validation, test).
-            subset (str):
-                Name of the configuration.
-
-        Returns:
-            List[Sample]:
-                Loaded split as a list of Sample objects.
-        """
-        feature_column = "text" if feature_column is None else feature_column
-        target_column = "label" if target_column is None else target_column
-        split = "test" if split is None else split
-
-        if subset:
-            dataset = self.load_dataset(self.dataset_name, name=subset, split=split)
-        else:
-            dataset = self.load_dataset(self.dataset_name, split=split)
-
-        dataset = dataset.map(
-            lambda example: {
-                "text": example[feature_column],
-                "label": example[target_column],
-            }
-        )
-
-        samples = [self._row_to_sample_classification(example) for example in dataset]
-        return samples
-
-    def load_data_summarization(
-        self,
-        feature_column: str,
-        target_column: str,
-        split: str,
-        subset: str = None,
-    ) -> List[Sample]:
-        """Load the specified split from the dataset for summarization task.
-
-        Args:
-            feature_column (str):
-                Name of the column containing the input text or document.
-            target_column (str):
-                Name of the column containing the target summary.
-            split (str):
-                Name of the split to load (e.g., train, validation, test).
-            subset (str):
-                Name of the configuration or subset to load.
-
-        Returns:
-            List[Sample]:
-                Loaded split as a list of Sample objects for summarization task.
-        """
-        feature_column = "document" if feature_column is None else feature_column
-        target_column = "summary" if target_column is None else target_column
-        split = "test" if split is None else split
-
-        if subset:
-            dataset = self.load_dataset(self.dataset_name, name=subset, split=split)
-        else:
-            dataset = self.load_dataset(self.dataset_name, split=split)
-
-        dataset = pd.DataFrame(dataset)
-
-        if feature_column not in dataset.columns:
-            raise ValueError(
-                f"feature_column '{feature_column}' not found in the dataset."
-            )
-        if target_column not in dataset.columns:
-            logging.warning(f"target_column '{target_column}' not found in the dataset.")
-            dataset["summary"] = None
-        else:
-            dataset.rename(columns={target_column: "summary"}, inplace=True)
-
-        dataset.rename(
-            columns={feature_column: "document"},
-            inplace=True,
-        )
-
-        samples = [
-            self._row_to_sample_summarization(row) for _, row in dataset.iterrows()
-        ]
-        return samples
-
-    def load_data_qa(
-        self,
-        feature_column: dict,
-        target_column: str,
-        split: str,
-        subset: str = None,
-    ) -> List[Sample]:
-        """Load the specified split from the dataset for QA task.
-
-        Args:
-            feature_column (str):
-                Name of the column containing the input question or passage.
-            target_column (str):
-                Name of the column containing the target answer.
-            split (str):
-                Name of the split to load (e.g., train, validation, test).
-            subset (str):
-                Name of the configuration or subset to load.
-
-        Returns:
-            List[Sample]:
-                Loaded split as a list of Sample objects for QA task.
-        """
-
-        if subset:
-            dataset = self.load_dataset(self.dataset_name, name=subset, split=split)
-
-        else:
-            dataset = self.load_dataset(self.dataset_name, split=split)
-
-        dataset = pd.DataFrame(dataset)
-
-        passage_column = feature_column.get("passage")
-        question_column = feature_column.get("question")
-
-        dataset_columns = set(dataset.columns)
-        if (
-            "question" not in feature_column
-            or feature_column["question"] not in dataset_columns
-        ):
-            raise ValueError(
-                f"'feature_column' '{feature_column['question']}' not found in the dataset."
-            )
-
-        if target_column not in dataset_columns:
-            logging.warning(f"target_column '{target_column}' not found in the dataset.")
-            dataset["answer"] = None
-        else:
-            dataset.rename(columns={target_column: "answer"}, inplace=True)
-
-        if passage_column:
-            if passage_column not in dataset_columns:
-                logging.warning(
-                    f"'feature_column' '{passage_column}' not found in the dataset."
-                )
-                dataset["passage"] = "-"
-            else:
-                dataset.rename(columns={passage_column: "passage"}, inplace=True)
-        else:
-            dataset["passage"] = "-"
-
-        if question_column in dataset.columns:
-            dataset.rename(columns={question_column: "question"}, inplace=True)
-
-        samples = [self._row_to_sample_qa(row) for _, row in dataset.iterrows()]
-        return samples
+            raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=self.LIB_NAME))
 
     def load_raw_data(
         self,
@@ -1745,6 +1429,7 @@ def load_data(
         target_column: Optional[str] = None,
         split: Optional[str] = None,
         subset: Optional[str] = None,
+        **kwargs,
     ) -> List[Sample]:
         """Load the specified data based on the task.
 
@@ -1766,60 +1451,24 @@ def load_data(
             ValueError:
                 If an unsupported task is provided.
         """
-        if self.task == "text-classification":
-            return self.load_data_classification(
-                feature_column, target_column, split, subset
-            )
-        elif self.task == "summarization":
-            return self.load_data_summarization(
-                feature_column, target_column, split, subset
-            )
-        elif self.task == "ner":
-            return self.load_data_ner(feature_column, target_column, split, subset)
-
-        elif self.task == "question-answering":
-            return self.load_data_qa(feature_column, target_column, split, subset)
+        feature_column = feature_column or self.source_info.get("feature_column", "text")
+        target_column = target_column or self.source_info.get("target_column", "label")
+        split = split or self.source_info.get("split", "test")
+        subset = subset or self.source_info.get("subset", None)
 
+        if subset:
+            dataset = self.load_dataset(self.dataset_name, name=subset, split=split)
         else:
-            raise ValueError(f"Unsupported task for HF datasets: {self.task}")
-
-    @staticmethod
-    def _row_to_sample_summarization(row: pd.Series) -> Sample:
-        """Convert a row from the dataset into a Sample for summarization.
-
-        Args:
-            data_row (Dict[str, str]):
-                Single row of the dataset.
-
-        Returns:
-            Sample:
-                Row formatted into a Sample object for summarization.
-        """
-        original = row.loc["document"]
-        summary = row.loc["summary"]
-
-        return SummarizationSample(original=original, expected_results=summary)
-
-    @staticmethod
-    def _row_to_sample_qa(row: pd.Series) -> QASample:
-        """Convert a row from the dataset into a Sample for summarization.
+            dataset = self.load_dataset(self.dataset_name, split=split)
 
-        Args:
-            data_row (Dict[str, str]):
-                Single row of the dataset.
+        data = []
+        for row_data in dataset:
+            sample = self.task.create_sample(
+                row_data, feature_column=feature_column, target_column=target_column
+            )
+            data.append(sample)
 
-        Returns:
-            Sample:
-                Row formatted into a Sample object for summarization.
-        """
-        question = row.loc["question"]
-        passage = row.loc["passage"]
-        answer = row.loc["answer"]
-        return QASample(
-            original_question=question,
-            original_context=passage,
-            expected_results=answer,
-        )
+        return data
 
     def export_data(self, data: List[Sample], output_path: str):
         """Exports the data to the corresponding format and saves it to 'output_path'.
@@ -1838,96 +1487,13 @@ def export_data(self, data: List[Sample], output_path: str):
         df = pd.DataFrame(rows, columns=list(self.COLUMN_NAMES[self.task].keys()))
         df.to_csv(output_path, index=False, encoding="utf-8")
 
-    def _row_to_sample_classification(self, data_row: Dict[str, str]) -> Sample:
-        """Convert a row from the dataset into a Sample for text classification.
-
-        Args:
-            data_row (Dict[str, str]):
-                Single row of the dataset.
-
-        Returns:
-            Sample:
-                Row formatted into a Sample object.
-        """
-        input_column = next(
-            (
-                col
-                for col in self.COLUMN_NAMES["text-classification"]["text"]
-                if col in data_row
-            ),
-            None,
-        )
-        output_column = next(
-            (
-                col
-                for col in self.COLUMN_NAMES["text-classification"]["label"]
-                if col in data_row
-            ),
-            None,
-        )
-
-        original = data_row.get(input_column, "")
-        label = SequenceLabel(label=data_row.get(output_column, ""), score=1)
-
-        return SequenceClassificationSample(
-            original=original,
-            expected_results=SequenceClassificationOutput(predictions=[label]),
-        )
-
-    def _row_to_ner_sample(self, data_row: dict) -> Sample:
-        """Convert a row from the dataset into a Sample for NER.
-
-        Args:
-            data_row (Dict[str, str]):
-                Single row of the dataset.
-
-        Returns:
-            Sample:
-                Row formatted into a Sample object.
-        """
-        input_column = next(
-            (col for col in self.COLUMN_NAMES["ner"]["text"] if col in data_row),
-            None,
-        )
-        output_column = next(
-            (col for col in self.COLUMN_NAMES["ner"]["ner"] if col in data_row),
-            None,
-        )
-
-        tokens = data_row.get(input_column, [])
-        labels = data_row.get(output_column, [])
-
-        #  get token and labels from the split
-        ner_labels = []
-        cursor = 0
-        for token, label in zip(tokens, labels):
-            ner_labels.append(
-                NERPrediction.from_span(
-                    entity=label,
-                    word=token,
-                    start=cursor,
-                    end=cursor + len(token),
-                    doc_id=0,
-                    doc_name="",
-                    pos_tag="XX",
-                    chunk_tag="XX",
-                )
-            )
-            # +1 to account for the white space
-            cursor += len(token) + 1
 
-        original = " ".join(tokens)
-        return NERSample(
-            original=original, expected_results=NEROutput(predictions=ner_labels)
-        )
-
-
-class SynteticDataset(_IDataset):
+class SynteticDataset(BaseDataset):
     """Example dataset class that loads data using the Hugging Face dataset library and also generates synthetic math data."""
 
     supported_tasks = ["sycophancy-test"]
 
-    def __init__(self, dataset: dict, task: str):
+    def __init__(self, dataset: dict, task: TaskManager):
         """
         Initialize the SynteticData class.
 
@@ -1979,13 +1545,12 @@ def load_data(self) -> List[Sample]:
             List[Sample]:
                 A list of Sample objects containing loaded data.
         """
-
-        if self.task == "sycophancy-test":
-            samples = getattr(self, f"load_{self.dataset_name.replace('-', '_')}")()
+        method_name = f"load_{self.dataset_name.replace('-', '_')}"
+        if hasattr(self, method_name):
+            samples = getattr(self, method_name)()
             return samples
-
         else:
-            raise ValueError(f"Unsupported task for HF datasets: {self.task}")
+            raise ValueError(Errors.E030.format(dataset_name=self.dataset_name))
 
     @staticmethod
     def extract_data_with_equal_proportion(data_dict, total_samples):
@@ -2077,7 +1642,11 @@ def load_synthetic_math_data(self) -> List[Sample]:
             for prompt, answer in self.prompt_to_answer.items()
         ]
         dataset = pd.DataFrame(data)
-        samples = [self._row_to_sample_sycophancy(row) for _, row in dataset.iterrows()]
+        dataset_name = self.dataset_name.replace("-", "").lower()
+        samples = [
+            self.task.create_sample(row, dataset_name=dataset_name)
+            for _, row in dataset.iterrows()
+        ]
         return samples
 
     def load_synthetic_nlp_data(self) -> List[Sample]:
@@ -2156,7 +1725,11 @@ def load_synthetic_nlp_data(self) -> List[Sample]:
             for prompt, answer in self.prompt_to_answer.items()
         ]
         dataset = pd.DataFrame(data)
-        samples = [self._row_to_sample_sycophancy(row) for _, row in dataset.iterrows()]
+        dataset_name = self.dataset_name.replace("-", "").lower()
+        samples = [
+            self.task.create_sample(row, dataset_name=dataset_name)
+            for row in dataset.to_dict(orient="records")
+        ]
         return samples
 
     def _row_to_sample_sycophancy(self, row: pd.Series) -> SycophancySample:
diff --git a/langtest/datahandler/format.py b/langtest/datahandler/format.py
index 72cacbaf7..75e8af01d 100644
--- a/langtest/datahandler/format.py
+++ b/langtest/datahandler/format.py
@@ -3,6 +3,7 @@
 from typing import List, Tuple, Union
 
 from ..utils.custom_types import NERSample, Sample, SequenceClassificationSample
+from ..errors import Errors
 
 
 class BaseFormatter(ABC):
@@ -80,7 +81,7 @@ def process(sample: Sample, output_format: str, *args, **kwargs):
                 sample, *args, **kwargs
             )
         except KeyError:
-            raise NameError(f"Class '{class_name}Formatter' not yet implemented.")
+            raise NameError(Errors.E031.format(class_name=class_name))
 
 
 class SequenceClassificationOutputFormatter(BaseFormatter, ABC):
diff --git a/langtest/embeddings/openai.py b/langtest/embeddings/openai.py
index 5ac3770c9..1f57b9de7 100644
--- a/langtest/embeddings/openai.py
+++ b/langtest/embeddings/openai.py
@@ -3,6 +3,7 @@
 import numpy as np
 from ..utils.lib_manager import try_import_lib
 from tenacity import retry, wait_random_exponential, stop_after_attempt
+from ..errors import Errors
 
 
 class OpenaiEmbeddings:
@@ -14,9 +15,7 @@ def __init__(self, model="text-embedding-ada-002"):
         self.openai = None
         self._check_openai_package()
         if not self.api_key:
-            raise ValueError(
-                "OpenAI API key not set. Please set the OPENAI_API_KEY environment variable."
-            )
+            raise ValueError(Errors.E032)
 
         self.openai.api_key = self.api_key
 
@@ -28,9 +27,7 @@ def _check_openai_package(self):
         if try_import_lib(self.LIB_NAME):
             self.openai = importlib.import_module(self.LIB_NAME)
         else:
-            raise ModuleNotFoundError(
-                f"The '{self.LIB_NAME}' package is not installed. Please install it using 'pip install {self.LIB_NAME}'."
-            )
+            raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=self.LIB_NAME))
 
     @retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6))
     def get_embedding(self, text: str) -> list[float]:
diff --git a/langtest/errors.py b/langtest/errors.py
new file mode 100644
index 000000000..fbe3da910
--- /dev/null
+++ b/langtest/errors.py
@@ -0,0 +1,233 @@
+class ErrorsWithCodes(type):
+    """
+    Metaclass that allows associating error/warning codes with messages.
+
+    This metaclass is used to create error and warning classes that provide error codes for easier identification
+    and retrieval of error/warning messages.
+
+    Args:
+        type (type): The metaclass to inherit from.
+
+    Methods:
+        __getattribute__(self, code): Retrieve the error/warning message associated with a given code.
+
+    Usage:
+        This metaclass is used to create error and warning classes, such as Errors and Warnings.
+    """
+
+    def __getattribute__(self, code):
+        """
+        Retrieve the error/warning message associated with a given code.
+
+        Args:
+            code (str): The error/warning code to look up.
+
+        Returns:
+            str: The formatted error/warning message.
+
+        Example:
+            error_message = Errors.E000
+        """
+        msg = super().__getattribute__(code)
+        if code.startswith("__"):
+            return msg
+        else:
+            return "[{code}] {msg}".format(code=code, msg=msg)
+
+
+class Warnings(metaclass=ErrorsWithCodes):
+    """
+    Class for defining warning messages and associating them with codes.
+
+    This class allows you to define and associate warning messages with unique codes for easier identification and retrieval.
+
+    Usage:
+        Create instances of this class to define custom warning messages.
+
+    Example:
+        class Warnings(metaclass=ErrorsWithCodes):
+            W000 = ("Please run `Harness.run()` before calling `.generated_results()`.")
+            W001 = ("No configuration file was provided, loading default config.")
+            # ...
+    """
+
+    W000 = ("Please run `Harness.run()` before calling `.generated_results()`.")
+    W001 = ("No configuration file was provided, loading default config.")
+    W002 = ("Default dataset '{info}' successfully loaded.")
+    W003 = "Filtering provided bias tests from {len_bias_data} samples - {len_samples_removed} samples removed "
+    W004 = (
+        "\n"
+        + "=" * 100
+        + "\nInvalid tokens found in sentence:\n {sent}. \nSkipping sentence.\n"
+        + "=" * 100
+        + "\n"
+    )
+    W005 = ("Skipping row {idx} due to invalid data: {row_data} - Exception: {e}")
+    W006 = ("target_column '{target_column}' not found in the dataset.")
+    W007 = ("'feature_column' '{passage_column}' not found in the dataset.")
+    W008 = ("Invalid or Missing label entries in the sentence: {sent}")
+    W009 = ("Removing samples where no transformation has been applied:\n")
+    W010 = ("- Test '{test}': {count} samples removed out of {total_sample}\n")
+    W011 = ("{class_name} successfully ran!")
+
+
+class Errors(metaclass=ErrorsWithCodes):
+    """
+    Class for defining error messages and associating them with codes.
+
+    This class allows you to define and associate error messages with unique codes for easier identification and retrieval.
+
+    Usage:
+        Create instances of this class to define custom error messages.
+
+    Example:
+        class Errors(metaclass=ErrorsWithCodes):
+            E000 = ("Each item in the list must be a dictionary")
+            E001 = ("Each dictionary in the list must have 'model' and 'hub' keys")
+            # ...
+    """
+
+    E000 = ("Each item in the list must be a dictionary")
+    E001 = ("Each dictionary in the list must have 'model' and 'hub' keys")
+    E002 = ("The dictionary must have 'model' and 'hub' keys")
+    E003 = ("Invalid 'model' parameter type")
+    E004 = (
+        "You haven't specified any value for the parameter 'data' and the configuration you "
+        "passed is not among the default ones. You need to either specify the parameter 'data' "
+        "or use a default configuration."
+    )
+    E005 = ("Please call .configure() first.")
+    E006 = ("Testcases are already generated, please call .run() and .report() next.")
+    E007 = ("Bias tests are not applicable for {data_source} dataset.")
+    E008 = ("{test_name} tests are not applicable for {data_source} dataset. Please use one of the following datasets: {selected_data_sources}")
+    E009 = ("Invalid test: {test_name}")
+    E010 = (
+        "The test casess have not been generated yet. Please use the `.generate()` method before"
+        "calling the `.run()` method."
+    )
+    E011 = (
+        "The tests have not been run yet. Please use the `.run()` method before"
+        "calling the `.report()` method."
+    )
+    E012 = ('You need to set "save_dir" parameter for this format.')
+    E013 = ('Report in format "{format}" is not supported. Please use "dataframe", "excel", "html", "markdown", "text", "dict".')
+    E014 = ("Custom proportions for {test_name} not found in the test types.")
+    E015 = (
+        "The current Harness has not been configured yet. Please use the `.configure` method "
+        "before calling the `.save` method."
+    )
+    E016 = (
+        "The test cases have not been generated yet. Please use the `.generate` method before"
+        "calling the `.save` method."
+    )
+    E017 = ("File '{filename}' is missing to load a previously saved `Harness`.")
+    E018 = ("Unsupported test type '{test_type}'. The available test types are: {available_tests}")
+    E019 = ("Invalid 'test_name' value '{test_name}'. It should be one of: Country-Economic-Bias, Religion-Bias, Ethnicity-Name-Bias, Gender-Pronoun-Bias.")
+    E020 = ("Invalid 'test_name' value '{test_name}'. It should be one of: Country-Economic-Representation, Religion-Representation, Ethnicity-Representation, Label-Representation.")
+    E021 = ("Invalid task type: {category}. Expected 'bias' or 'representation'.")
+    E022 = ("A valid token is required for Hugging Face Hub authentication.")
+    E023 = ("The '{LIB_NAME}' package is not installed. Please install it using 'pip install {LIB_NAME}'.")
+    E024 = ("'file_path' must be a dictionary.")
+    E025 = ("The 'data_source' key must be provided in the 'file_path' dictionary.")
+    E026 = ("Given task ({task}) is not matched with template. \
+            CSV dataset can ne only loaded for text-classification and ner!")
+    E027 = ("feature_column '{feature_column}' not found in the dataset.")
+    E028 = (
+        "CSV file is invalid. CSV handler works with template column names!\n"
+        "{not_referenced_columns_keys} column could not be found in header.\n"
+        "You can use following namespaces:\n{not_referenced_columns}"
+    )
+    E029 = (
+        "Your dataset needs to have at least have a column with one of the following name: "
+        "{valid_column_names}, found: {column_names}."
+    )
+    E030 = ("Invalid dataset name: {dataset_name}")
+    E031 = ("Class '{class_name}Formatter' not yet implemented.")
+    E032 = ("OpenAI API key not set. Please set the OPENAI_API_KEY environment variable.")
+    E033 = ("Input arrays must be of type 'np.ndarray', but received types: {type_a} and {type_b}")
+    E034 = ("Distance function '{name}' not found")
+    E035 = (
+        "Input strings must be of type 'str', but received types: {type_a} and {type_b}"
+    )
+    E036 = ("String distance function '{name}' not found")
+    E037 = "Model must have a predict method"
+    E038 = (
+        "Invalid SparkNLP model object: {model_type}. "
+        "John Snow Labs model handler accepts: "
+        "[NLUPipeline, PretrainedPipeline, PipelineModel, LightPipeline]"
+    )
+    E039 = (
+        "johnsnowlabs is not installed. "
+        "In order to use NLP Models Hub, johnsnowlabs should be installed!"
+    )
+    E040 = ("Invalid PipelineModel! There should be at least one {var} component.")
+    E041 = ("Model '{path}' is not found online or local. Please install it by python -m spacy download {path} or check the path.")
+    E042 = ("Provided model hub is not supported. Please choose one of the supported model hubs: {supported_hubs}")
+    E043 = ("Provided task is not supported. Please choose one of the supported tasks: {l}")
+    E044 = ("Model '{path}' is not found online or local. Please install langchain by pip install langchain")
+    E045 = ("Please update model_parameters section in config.yml file for '{path}' model in '{hub}'.\nmodel_parameters:\n\t{field}: value\n\n{required_fields} is/are required field(s), please provide them in config.yml")
+    E046 = ('The test type "{sub_test}" is not supported for the task "{task}". "{sub_test}" only supports {supported}.')
+    E047 = ("The test category {test_category} does not exist. Available categories are: {available_categories}.")
+    E048 = ("Invalid test configuration! Tests can be [1] dictionary of test name and corresponding parameters.")
+    E049 = ("Invalid test specification: {not_supported_tests}. Available tests are: {supported_tests}")
+    E050 = ("Invalid perturbation {key} in multiple_perturbations. Please use perturbations1, perturbations2, ...")
+    E051 = ("multiple_perturbations test is not supported for NER task")
+    E052 = ("This dataset does not contain labels, and {var} tests cannot be run with it.")
+    E053 = ("The dataset {dataset_name} does not contain labels, and fairness tests cannot be run with it. Skipping the fairness tests.")
+    E054 = ("Invalid schema. It should be one of: {var}.")
+    E055 = ("Invalid JSON format. 'name' key is missing.")
+    E056 = ("Invalid 'name' value '{var1}'. It should be one of: {var2}.")
+    E057 = ("Invalid 'name' format in the JSON file.")
+    E058 = ("At least one of 'first_names' or 'last_names' must be specified for '{name}'.")
+    E059 = ("'last_names' must be specified for '{name}'.")
+    E060 = (
+        "Invalid keys in the JSON for '{name}'. "
+        "Only the following keys are allowed: 'name', 'first_names', 'last_names'."
+    )
+    E061 = (
+        "Missing pronoun keys in the JSON for '{name}'. Please include at least one of: "
+        "'subjective_pronouns', 'objective_pronouns', 'reflexive_pronouns', 'possessive_pronouns'."
+    )
+    E062 = (
+        "Invalid keys in the JSON for '{var1}': {var2}. "
+        "Only the following keys are allowed: "
+        "'name', 'subjective_pronouns', 'objective_pronouns', 'reflexive_pronouns', 'possessive_pronouns'."
+    )
+    E063 = ("This method must be implemented in the derived class.")
+    E064 = ("Sum of proportions cannot be greater than 1. So {var} test cannot run.")
+    E065 = ("In order to generate test cases for swap_entities, {var} should be passed!")
+    E066 = ("Add context strategy must be one of 'start', 'end', 'combined'. Cannot be {strategy}.")
+    E067 = ("Unknown transformation: {order}")
+    E068 = ("Invalid JSON format. Format should be a list of strings.")
+    E069 = ("pass_custom_data() method with test_name as 'Label-Representation' is only supported for NER task.")
+    E070 = ("Unsupported task: '{var}'")
+    E071 = (
+        "You are trying to access a gated repo. Make sure to request access at "
+        "{model_name} and pass a token having permission to this repo either by logging in with `huggingface-cli login` or by setting the `HUGGINGFACEHUB_API_TOKEN` environment variable with your API token."
+    )
+    E072 = ("Number out of range")
+    E073 = (
+        "Dictionary Error: Search term or search token not found in dictionary. "
+        "Contact administrator to update dictionary if necessary."
+    )
+    E074 = ("Invalid averaging method. Must be one of 'macro', 'micro', or 'weighted'.")
+    E075 = ("No {hub_name} embeddings class found")
+    E076 = ("Unsupported {metric} distance metric: {selected_metric}")
+    E077 = ("\nThe provided columns are not supported for creating a sample.\
+            \nPlease choose one of the supported columns: {supported_columns}\
+            \nOr classify the features and target columns from the {given_columns}")
+
+
+class ColumnNameError(Exception):
+    """ColumnNameError class is used to raise an exception
+    when the column name is not found in the dataset."""
+
+    def __init__(
+        self,
+        supported_columns,
+        given_columns,
+    ):
+        self.message = Errors.E077.format(
+            supported_columns=supported_columns, given_columns=given_columns
+        )
+        super().__init__(self.message)
diff --git a/langtest/langtest.py b/langtest/langtest.py
index 79993fb78..a2af1da3a 100644
--- a/langtest/langtest.py
+++ b/langtest/langtest.py
@@ -7,17 +7,23 @@
 from collections import defaultdict
 from typing import Dict, List, Optional, Union
 
-import matplotlib.pyplot as plt
 import pandas as pd
 import yaml
+
+
 from pkg_resources import resource_filename
+from langtest.modelhandler import ModelAPI
 
+from .tasks import TaskManager
 from .augmentation import AugmentRobustness, TemplaticAugment
-from .datahandler.datasource import DataFactory, HuggingFaceDataset, SynteticDataset
-from .modelhandler import LANGCHAIN_HUBS, ModelFactory
+from .datahandler.datasource import DataFactory
+from .modelhandler import LANGCHAIN_HUBS
 from .transform import TestFactory
+from .utils import report_utils as report
+
 from .transform.utils import RepresentationOperation
 from langtest.utils.lib_manager import try_import_lib
+from .errors import Warnings, Errors
 
 GLOBAL_MODEL = None
 GLOBAL_HUB = None
@@ -185,19 +191,18 @@ def __init__(
 
         self.is_default = False
 
+        # loading model and hub
         if isinstance(model, list):
             for item in model:
                 if not isinstance(item, dict):
-                    raise ValueError("Each item in the list must be a dictionary")
+                    raise ValueError(Errors.E000)
                 if "model" not in item or "hub" not in item:
-                    raise ValueError(
-                        "Each dictionary in the list must have 'model' and 'hub' keys"
-                    )
+                    raise ValueError(Errors.E001)
         elif isinstance(model, dict):
             if "model" not in model or "hub" not in model:
-                raise ValueError("The dictionary must have 'model' and 'hub' keys")
+                raise ValueError(Errors.E002)
         else:
-            raise ValueError("Invalid 'model' parameter type")
+            raise ValueError(Errors.E003)
 
         if isinstance(model, dict):
             hub, model = model["hub"], model["model"]
@@ -206,23 +211,11 @@ def __init__(
         else:
             hub = None
 
-        if task not in self.SUPPORTED_TASKS:
-            raise ValueError(
-                f"Provided task is not supported. Please choose one of the supported tasks: {self.SUPPORTED_TASKS}"
-            )
-        self.task = task
-
-        if isinstance(model, str) and hub is None:
-            raise ValueError(
-                "When passing a string argument to the 'model' parameter, you must provide an argument "
-                "for the 'hub' parameter as well."
-            )
+        # loading task
 
-        if hub is not None and hub not in self.SUPPORTED_HUBS:
-            raise ValueError(
-                f"Provided hub is not supported. Please choose one of the supported hubs: {self.SUPPORTED_HUBS}"
-            )
+        self.task = TaskManager(task)
 
+        # Loading default datasets
         if data is None and (task, model, hub) in self.DEFAULTS_DATASET:
             data_path = os.path.join("data", self.DEFAULTS_DATASET[(task, model, hub)])
             data = {"data_source": resource_filename("langtest", data_path)}
@@ -230,91 +223,17 @@ def __init__(
             if model == "textcat_imdb":
                 model = resource_filename("langtest", "data/textcat_imdb")
             self.is_default = True
-            logging.info("Default dataset '%s' successfully loaded.", (task, model, hub))
-
-        elif (
-            isinstance(data, dict)
-            and "source" in data
-            and data["source"] == "huggingface"
-        ):
-            if (
-                task == "text-classification"
-                and hub in self.SUPPORTED_HUBS_HF_DATASET_CLASSIFICATION
-            ):
-                self.data = HuggingFaceDataset(data["data_source"], task=task).load_data(
-                    feature_column=data.get("feature_column", "text"),
-                    target_column=data.get("target_column", "label"),
-                    split=data.get("split", "test"),
-                    subset=data.get("subset", None),
-                )
+            logging.info(Warnings.W002.format(info=(task, model, hub)))
 
-                if hub == "spacy" and (model == "textcat_imdb" or model is None):
-                    if model is None:
-                        logging.warning(
-                            "Using the default 'textcat_imdb' model for Spacy hub. Please provide a custom model path if desired."
-                        )
-                    model = resource_filename("langtest", "data/textcat_imdb")
-
-            elif task == "ner" and hub in self.SUPPORTED_HUBS_HF_DATASET_NER:
-                self.data = HuggingFaceDataset(data["data_source"], task=task).load_data(
-                    feature_column=data.get("feature_column", "tokens"),
-                    target_column=data.get("target_column", "ner_tags"),
-                    split=data.get("split", "test"),
-                    subset=data.get("subset", None),
-                )
-            elif (
-                task == "question-answering" and hub in self.SUPPORTED_HUBS_HF_DATASET_LLM
-            ):
-                self.data = HuggingFaceDataset(data["data_source"], task=task).load_data(
-                    feature_column=data.get(
-                        "feature_column", {"passage": "passage", "question": "question"}
-                    ),
-                    target_column=data.get("target_column", "answer"),
-                    split=data.get("split", "test"),
-                    subset=data.get("subset", None),
-                )
-
-            elif task == "summarization" and hub in self.SUPPORTED_HUBS_HF_DATASET_LLM:
-                self.data = HuggingFaceDataset(data["data_source"], task=task).load_data(
-                    feature_column=data.get("feature_column", "document"),
-                    target_column=data.get("target_column", "summary"),
-                    split=data.get("split", "test"),
-                    subset=data.get("subset", None),
-                )
-        elif data is None and task == "political":
-            self.data = []
         elif data is None and (task, model, hub) not in self.DEFAULTS_DATASET.keys():
-            raise ValueError(
-                "You haven't specified any value for the parameter 'data' and the configuration you "
-                "passed is not among the default ones. You need to either specify the parameter 'data' "
-                "or use a default configuration."
-            )
-        elif (
-            isinstance(data, dict)
-            and data["data_source"] in ("BoolQ-bias", "XSum-bias")
-            and task in ("question-answering", "summarization")
-        ):
-            self.data = DataFactory.load_curated_bias(data["data_source"])
-        elif (
-            isinstance(data, dict)
-            and data["data_source"] in ("synthetic-nlp-data", "synthetic-math-data")
-            and task in ("sycophancy-test")
-        ):
-            self.data = SynteticDataset(data, task=task).load_data()
-
-        elif isinstance(data["data_source"], list):
-            self.data = data["data_source"]
+            raise ValueError(Errors.E004)
+
+        if isinstance(data.get("data_source"), list):
+            self.data = data.get("data_source")
         else:
-            if "data_source" not in data:
-                raise ValueError(
-                    "The 'data_source' key must be provided in the 'data' parameter."
-                )
-            self.file_path = data["data_source"]
-            self.data = (
-                DataFactory(data, task=self.task).load() if data is not None else None
-            )
-        self.data_source = data.get("data_source") if data else None
+            self.data = DataFactory(data, task=self.task).load()
 
+        # config loading
         if config is not None:
             self._config = self.configure(config)
         elif task in self.DEFAULTS_CONFIG:
@@ -323,38 +242,29 @@ def __init__(
             elif isinstance(self.DEFAULTS_CONFIG[task], str):
                 self._config = self.configure(self.DEFAULTS_CONFIG[task])
         else:
-            logging.info("No configuration file was provided, loading default config.")
+            logging.info(Warnings.W001)
             self._config = self.configure(
                 resource_filename("langtest", "data/config.yml")
             )
 
-        if isinstance(model, str):
-            self.model = ModelFactory.load_model(
-                path=model, task=task, hub=hub, **self._config.get("model_parameters", {})
-            )
-
-        elif isinstance(model, list):
+        # model section
+        if isinstance(model, list):
             model_dict = {}
             for i in model:
                 model = i["model"]
                 hub = i["hub"]
 
-                model_dict[model] = ModelFactory.load_model(
-                    path=model,
-                    task=task,
-                    hub=hub,
-                    **self._config.get("model_parameters", {}),
+                model_dict[model] = self.task.model(
+                    model, hub, **self._config.get("model_parameters", {})
                 )
+
                 self.model = model_dict
 
         else:
-            self.model = ModelFactory(
-                task=task,
-                model=model,
-                hub=hub,
-                **self._config.get("model_parameters", {}),
+            self.model = self.task.model(
+                model, hub, **self._config.get("model_parameters", {})
             )
-
+        # end model selection
         formatted_config = json.dumps(self._config, indent=1)
         print("Test Configuration : \n", formatted_config)
 
@@ -395,42 +305,6 @@ def configure(self, config: Union[str, dict]) -> dict:
 
         global HARNESS_CONFIG
         HARNESS_CONFIG = self._config
-        model = GLOBAL_MODEL
-
-        if self.task == "translation" and model:
-            hub = self.hub
-            model = self._actual_model
-            task = self.task
-
-            if isinstance(model, str):
-                self.model = ModelFactory.load_model(
-                    path=model,
-                    task=task,
-                    hub=hub,
-                    **self._config.get("model_parameters", {}),
-                )
-
-            elif isinstance(model, list):
-                model_dict = {}
-
-                for i in model:
-                    model = i["model"]
-                    hub = i["hub"]
-
-                    model_dict[model] = ModelFactory.load_model(
-                        path=model,
-                        task=task,
-                        hub=hub,
-                        **self._config.get("model_parameters", {}),
-                    )
-                self.model = model_dict
-            else:
-                self.model = ModelFactory(
-                    task=task,
-                    model=model,
-                    hub=hub,
-                    **self._config.get("model_parameters", {}),
-                )
 
         return self._config
 
@@ -440,11 +314,9 @@ def generate(self) -> "Harness":
         The generated testcases are stored in `_testcases` attribute.
         """
         if self._config is None:
-            raise RuntimeError("Please call .configure() first.")
+            raise RuntimeError(Errors.E005)
         if self._testcases is not None:
-            raise RuntimeError(
-                "Testcases are already generated, please call .run() and .report() next."
-            )
+            raise RuntimeError(Errors.E006)
 
         tests = self._config["tests"]
         m_data = [sample.copy() for sample in self.data]
@@ -483,9 +355,7 @@ def generate(self) -> "Harness":
                         self._testcases.extend(other_testcases)
                     return self
                 else:
-                    raise ValueError(
-                        f"Bias tests are not applicable for {self.data_source} dataset."
-                    )
+                    raise ValueError(Errors.E007.format(data_source=self.data_source))
             else:
                 self._testcases = TestFactory.transform(
                     self.task, self.data, tests, m_data=m_data
@@ -517,10 +387,14 @@ def generate(self) -> "Harness":
                     return self
                 else:
                     raise ValueError(
-                        f"{test_name} tests are not applicable for {self.data_source} dataset. Please use one of the following datasets: {selected_data_sources}"
+                        Errors.E008.format(
+                            test_name=test_name,
+                            data_source=self.data_source,
+                            selected_data_sources=selected_data_sources,
+                        )
                     )
             else:
-                raise ValueError(f"Invalid test: {test_name}")
+                raise ValueError(Errors.E009.format(test_name=test_name))
 
         self._testcases = TestFactory.transform(
             self.task, self.data, tests, m_data=m_data
@@ -534,10 +408,7 @@ def run(self) -> "Harness":
             None: The evaluations are stored in `generated_results` attribute.
         """
         if self._testcases is None:
-            raise RuntimeError(
-                "The test casess have not been generated yet. Please use the `.generate()` method before"
-                "calling the `.run()` method."
-            )
+            raise RuntimeError(Errors.E010)
         if not isinstance(self._testcases, dict):
             self._generated_results = TestFactory.run(
                 self._testcases,
@@ -576,10 +447,7 @@ def report(
                 DataFrame containing the results of the tests.
         """
         if self._generated_results is None:
-            raise RuntimeError(
-                "The tests have not been run yet. Please use the `.run()` method before"
-                "calling the `.report()` method."
-            )
+            raise RuntimeError(Errors.E011)
 
         if isinstance(self._config, dict):
             self.default_min_pass_dict = self._config["tests"]["defaults"].get(
@@ -595,304 +463,45 @@ def report(
         summary = defaultdict(lambda: defaultdict(int))
 
         if self.task == "political":
-            econ_score = 0.0
-            econ_count = 0.0
-            social_score = 0.0
-            social_count = 0.0
-            for sample in self._generated_results:
-                if sample.test_case == "right":
-                    econ_score += sample.is_pass
-                    econ_count += 1
-                elif sample.test_case == "left":
-                    econ_score -= sample.is_pass
-                    econ_count += 1
-                elif sample.test_case == "auth":
-                    social_score += sample.is_pass
-                    social_count += 1
-                elif sample.test_case == "lib":
-                    social_score -= sample.is_pass
-                    social_count += 1
-
-            econ_score /= econ_count
-            social_score /= social_count
-
-            report = {}
-
-            report["political_economic"] = {
-                "category": "political",
-                "score": econ_score,
-            }
-            report["political_social"] = {
-                "category": "political",
-                "score": social_score,
-            }
-            df_report = pd.DataFrame.from_dict(report, orient="index")
-            df_report = df_report.reset_index().rename(columns={"index": "test_type"})
-
-            col_to_move = "category"
-            first_column = df_report.pop("category")
-            df_report.insert(0, col_to_move, first_column)
-            df_report = df_report.reset_index(drop=True)
-
-            self.df_report = df_report.fillna("-")
-
-            plt.scatter(econ_score, social_score, color="red")
-            plt.xlim(-1, 1)
-            plt.ylim(-1, 1)
-            plt.title("Political coordinates")
-            plt.xlabel("Economic Left/Right")
-            plt.ylabel("Social Libertarian/Authoritarian")
-
-            plt.axhline(y=0, color="k")
-            plt.axvline(x=0, color="k")
-
-            plt.axvspan(0, 1, 0.5, 1, color="blue", alpha=0.4)
-            plt.axvspan(-1, 0, 0.5, 1, color="red", alpha=0.4)
-            plt.axvspan(0, 1, -1, 0.5, color="yellow", alpha=0.4)
-            plt.axvspan(-1, 0, -1, 0.5, color="green", alpha=0.4)
-
-            plt.grid()
-
-            plt.show()
-
+            self.df_report = report.political_report(self._generated_results)
             return self.df_report
 
         elif not isinstance(self._generated_results, dict):
-            for sample in self._generated_results:
-                summary[sample.test_type]["category"] = sample.category
-                summary[sample.test_type][str(sample.is_pass()).lower()] += 1
-            report = {}
-            for test_type, value in summary.items():
-                pass_rate = summary[test_type]["true"] / (
-                    summary[test_type]["true"] + summary[test_type]["false"]
-                )
-                min_pass_rate = self.min_pass_dict.get(
-                    test_type, self.default_min_pass_dict
-                )
-
-                if "-" in test_type and summary[test_type]["category"] == "robustness":
-                    multiple_perturbations_min_pass_rate = self.min_pass_dict.get(
-                        "multiple_perturbations", self.default_min_pass_dict
-                    )
-                    min_pass_rate = self.min_pass_dict.get(
-                        test_type, multiple_perturbations_min_pass_rate
-                    )
-                if summary[test_type]["category"] in ["Accuracy", "performance"]:
-                    min_pass_rate = 1
-
-                report[test_type] = {
-                    "category": summary[test_type]["category"],
-                    "fail_count": summary[test_type]["false"],
-                    "pass_count": summary[test_type]["true"],
-                    "pass_rate": pass_rate,
-                    "minimum_pass_rate": min_pass_rate,
-                    "pass": pass_rate >= min_pass_rate,
-                }
-
-            df_report = pd.DataFrame.from_dict(report, orient="index")
-            df_report = df_report.reset_index().rename(columns={"index": "test_type"})
-
-            df_report["pass_rate"] = df_report["pass_rate"].apply(
-                lambda x: "{:.0f}%".format(x * 100)
-            )
-            df_report["minimum_pass_rate"] = df_report["minimum_pass_rate"].apply(
-                lambda x: "{:.0f}%".format(x * 100)
+            self.df_report = report.model_report(
+                summary,
+                self.min_pass_dict,
+                self.default_min_pass_dict,
+                self._generated_results,
             )
-            col_to_move = "category"
-            first_column = df_report.pop("category")
-            df_report.insert(0, col_to_move, first_column)
-            df_report = df_report.reset_index(drop=True)
-
-            self.df_report = df_report.fillna("-")
 
             if mlflow_tracking:
-                try:
-                    import mlflow
-                except ModuleNotFoundError:
-                    print("mlflow package not found. Install mlflow first")
-
-                import datetime
-
                 experiment_name = (
                     self._actual_model
                     if isinstance(self._actual_model, str)
                     else self._actual_model.__class__.__module__
                 )
 
-                # Get the experiment
-                experiment = mlflow.get_experiment_by_name(experiment_name)
-
-                if experiment is None:
-                    # The experiment does not exist, create it
-                    experiment_id = mlflow.create_experiment(experiment_name)
-                else:
-                    # The experiment exists, get its ID
-                    experiment_id = experiment.experiment_id
+                report.mlflow_report(experiment_name, self.task, self.df_report)
 
-                current_datetime = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
-                mlflow.start_run(
-                    run_name=self.task + "_testing_" + current_datetime,
-                    experiment_id=experiment_id,
-                )
-
-                df_report.apply(
-                    lambda row: mlflow.log_metric(
-                        row["test_type"] + "_pass_rate",
-                        float(row["pass_rate"].rstrip("%")) / 100,
-                    ),
-                    axis=1,
-                )
-                df_report.apply(
-                    lambda row: mlflow.log_metric(
-                        row["test_type"] + "_min_pass_rate",
-                        float(row["minimum_pass_rate"].rstrip("%")) / 100,
-                    ),
-                    axis=1,
-                )
-                df_report.apply(
-                    lambda row: mlflow.log_metric(
-                        row["test_type"] + "_pass_status", 1 if row["pass"] else 0
-                    ),
-                    axis=1,
-                )
-                df_report.apply(
-                    lambda row: mlflow.log_metric(
-                        row["test_type"] + "_pass_count", row["pass_count"]
-                    ),
-                    axis=1,
-                )
-                df_report.apply(
-                    lambda row: mlflow.log_metric(
-                        row["test_type"] + "_fail_count", row["fail_count"]
-                    ),
-                    axis=1,
-                )
-                mlflow.end_run()
-
-            if format == "dataframe":
-                return self.df_report
-            elif format == "dict":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                self.df_report.to_json(save_dir)
-            elif format == "excel":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                self.df_report.to_excel(save_dir)
-            elif format == "html":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                self.df_report.to_html(save_dir)
-            elif format == "markdown":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                self.df_report.to_markdown(save_dir)
-            elif format == "text" or format == "csv":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                self.df_report.to_csv(save_dir)
-            else:
-                raise ValueError(
-                    f'Report in format "{format}" is not supported. Please use "dataframe", "excel", "html", "markdown", "text", "dict".'
-                )
+            report.save_format(format, save_dir, self.df_report)
+            return self.df_report
 
         else:
             df_final_report = pd.DataFrame()
-            for k, v in self.model.items():
-                for sample in self._generated_results[k]:
-                    summary[sample.test_type]["category"] = sample.category
-                    summary[sample.test_type][str(sample.is_pass()).lower()] += 1
-                report = {}
-                for test_type, value in summary.items():
-                    pass_rate = summary[test_type]["true"] / (
-                        summary[test_type]["true"] + summary[test_type]["false"]
-                    )
-                    min_pass_rate = self.min_pass_dict.get(
-                        test_type, self.default_min_pass_dict
-                    )
-
-                    if summary[test_type]["category"] in ["Accuracy", "performance"]:
-                        min_pass_rate = 1
-
-                    report[test_type] = {
-                        "model_name": k,
-                        "pass_rate": pass_rate,
-                        "minimum_pass_rate": min_pass_rate,
-                        "pass": pass_rate >= min_pass_rate,
-                    }
-
-                df_report = pd.DataFrame.from_dict(report, orient="index")
-                df_report = df_report.reset_index().rename(columns={"index": "test_type"})
-
-                df_report["pass_rate"] = df_report["pass_rate"].apply(
-                    lambda x: "{:.0f}%".format(x * 100)
-                )
-                df_report["minimum_pass_rate"] = df_report["minimum_pass_rate"].apply(
-                    lambda x: "{:.0f}%".format(x * 100)
+            for k in self.model.keys():
+                df_report = report.multi_model_report(
+                    summary,
+                    self.min_pass_dict,
+                    self.default_min_pass_dict,
+                    self._generated_results,
+                    k,
                 )
-
-                df_report = df_report.reset_index(drop=True)
-                df_report = df_report.fillna("-")
                 if mlflow_tracking:
-                    try:
-                        import mlflow
-                    except ModuleNotFoundError:
-                        print("mlflow package not found. Install mlflow first")
-
-                    import datetime
-
                     experiment_name = k
-
-                    # Get the experiment
-                    experiment = mlflow.get_experiment_by_name(experiment_name)
-
-                    if experiment is None:
-                        # The experiment does not exist, create it
-                        experiment_id = mlflow.create_experiment(experiment_name)
-                    else:
-                        # The experiment exists, get its ID
-                        experiment_id = experiment.experiment_id
-
-                    current_datetime = datetime.datetime.now().strftime(
-                        "%Y-%m-%d_%H-%M-%S"
-                    )
-                    mlflow.start_run(
-                        run_name=self.task + "_testing_" + current_datetime,
-                        experiment_id=experiment_id,
+                    report.mlflow_report(
+                        experiment_name, self.task, df_report, multi_model_comparison=True
                     )
 
-                    df_report.apply(
-                        lambda row: mlflow.log_metric(
-                            row["test_type"] + "_pass_rate",
-                            float(row["pass_rate"].rstrip("%")) / 100,
-                        ),
-                        axis=1,
-                    )
-                    df_report.apply(
-                        lambda row: mlflow.log_metric(
-                            row["test_type"] + "_min_pass_rate",
-                            float(row["minimum_pass_rate"].rstrip("%")) / 100,
-                        ),
-                        axis=1,
-                    )
-                    df_report.apply(
-                        lambda row: mlflow.log_metric(
-                            row["test_type"] + "_pass_status", 1 if row["pass"] else 0
-                        ),
-                        axis=1,
-                    )
-                    mlflow.end_run()
-
                 df_final_report = pd.concat([df_final_report, df_report])
 
             df_final_report["minimum_pass_rate"] = (
@@ -911,54 +520,15 @@ def report(
                 aggfunc="mean",
             )
 
-            def color_cells(series):
-                res = []
-                for x in series.index:
-                    res.append(
-                        df_final_report[
-                            (df_final_report["test_type"] == series.name)
-                            & (df_final_report["model_name"] == x)
-                        ]["pass"].all()
-                    )
-                return [
-                    "background-color: green" if x else "background-color: red"
-                    for x in res
-                ]
-
-            styled_df = pivot_df.style.apply(color_cells)
+            styled_df = pivot_df.style.apply(
+                report.color_cells, df_final_report=df_final_report
+            )
 
             if format == "dataframe":
                 return styled_df
-            elif format == "dict":
-                return styled_df.to_dict("records")
-            elif format == "excel":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                styled_df.to_excel(save_dir)
-            elif format == "html":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                styled_df.to_html(save_dir)
-            elif format == "markdown":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                styled_df.to_markdown(save_dir)
-            elif format == "text" or format == "csv":
-                if save_dir is None:
-                    raise ValueError(
-                        'You need to set "save_dir" parameter for this format.'
-                    )
-                styled_df.to_csv(save_dir)
+
             else:
-                raise ValueError(
-                    f'Report in format "{format}" is not supported. Please use "dataframe", "excel", "html", "markdown", "text", "dict".'
-                )
+                report.save_format(format, save_dir, styled_df)
 
     def generated_results(self) -> Optional[pd.DataFrame]:
         """Generates an overall report with every textcase and labelwise metrics.
@@ -967,9 +537,7 @@ def generated_results(self) -> Optional[pd.DataFrame]:
             pd.DataFrame: Generated dataframe.
         """
         if self._generated_results is None:
-            logging.warning(
-                "Please run `Harness.run()` before calling `.generated_results()`."
-            )
+            logging.warning(Warnings.W000)
             return
 
         if isinstance(self._generated_results, dict):
@@ -1118,7 +686,7 @@ def augment(
 
             if not (vaild_test_types.issubset(report_test_types)):
                 raise ValueError(
-                    f"Custom proportions for {vaild_test_types - report_test_types} not found in the test types."
+                    Errors.E014.format(test_name=(vaild_test_types - report_test_types))
                 )
 
         if templates:
@@ -1232,16 +800,10 @@ def save(self, save_dir: str) -> None:
 
         """
         if self._config is None:
-            raise RuntimeError(
-                "The current Harness has not been configured yet. Please use the `.configure` method "
-                "before calling the `.save` method."
-            )
+            raise RuntimeError(Errors.E015)
 
         if self._testcases is None:
-            raise RuntimeError(
-                "The test cases have not been generated yet. Please use the `.generate` method before"
-                "calling the `.save` method."
-            )
+            raise RuntimeError(Errors.E016)
 
         if not os.path.isdir(save_dir):
             os.mkdir(save_dir)
@@ -1259,7 +821,7 @@ def save(self, save_dir: str) -> None:
     def load(
         cls,
         save_dir: str,
-        model: Union[str, "ModelFactory"],
+        model: Union[str, "ModelAPI"],
         task: str,
         hub: Optional[str] = None,
     ) -> "Harness":
@@ -1270,8 +832,8 @@ def load(
                 path to folder containing all the needed files to load an saved `Harness`
             task (str):
                 task for which the model is to be evaluated.
-            model (str | ModelFactory):
-                ModelFactory object or path to the model to be evaluated.
+            model (str | ModelAPI):
+                ModelAPI object or path to the model to be evaluated.
             hub (str, optional):
                 model hub to load from the path. Required if path is passed as 'model'.
 
@@ -1281,9 +843,7 @@ def load(
         """
         for filename in ["config.yaml", "test_cases.pkl", "data.pkl"]:
             if not os.path.exists(os.path.join(save_dir, filename)):
-                raise OSError(
-                    f"File '{filename}' is missing to load a previously saved `Harness`."
-                )
+                raise OSError(Errors.E017)
 
         with open(os.path.join(save_dir, "data.pkl"), "rb") as reader:
             data = pickle.load(reader)
@@ -1350,7 +910,9 @@ def available_tests(test_type: str = None) -> Dict[str, List[str]]:
         if test_type:
             if test_type not in available_tests.keys():
                 raise ValueError(
-                    f"Unsupported test type '{test_type}'. The available test types are: {available_tests.keys()}"
+                    Errors.E018.format(
+                        test_type=test_type, available_tests=available_tests.keys()
+                    )
                 )
             return {test_type: available_tests[test_type]}
 
@@ -1381,9 +943,7 @@ def pass_custom_data(
                 "Ethnicity-Name-Bias",
                 "Gender-Pronoun-Bias",
             ):
-                raise ValueError(
-                    f"Invalid 'test_name' value '{test_name}'. It should be one of: Country-Economic-Bias, Religion-Bias, Ethnicity-Name-Bias, Gender-Pronoun-Bias."
-                )
+                raise ValueError(Errors.E019.format(test_name=test_name))
 
             TestFactory.call_add_custom_bias(data, test_name, append)
         elif task == "representation":
@@ -1393,18 +953,14 @@ def pass_custom_data(
                 "Ethnicity-Representation",
                 "Label-Representation",
             ):
-                raise ValueError(
-                    f"Invalid 'test_name' value '{test_name}'. It should be one of: Country-Economic-Representation, Religion-Representation, Ethnicity-Representation, Label-Representation."
-                )
+                raise ValueError(Errors.E020.format(test_name=test_name))
 
             RepresentationOperation.add_custom_representation(
                 data, test_name, append, check=self.task
             )
 
         else:
-            raise ValueError(
-                f"Invalid task type: {task}. Expected 'bias' or 'representation'."
-            )
+            raise ValueError(Errors.E021.format(category=task))
 
     def upload_folder_to_hub(
         repo_name: str,
@@ -1435,9 +991,7 @@ def upload_folder_to_hub(
                                 model_type ("huggingface" or "spacy").
         """
         if token is None:
-            raise ValueError(
-                "A valid token is required for Hugging Face Hub authentication."
-            )
+            raise ValueError(Errors.E022)
         subprocess.run(["huggingface-cli", "login", "--token", token], check=True)
 
         if (
@@ -1449,10 +1003,7 @@ def upload_folder_to_hub(
                 huggingface_hub = importlib.import_module(LIB_NAME)
                 HfApi = getattr(huggingface_hub, "HfApi")
             else:
-                raise ModuleNotFoundError(
-                    f"The '{LIB_NAME}' package is not installed. Please install it using 'pip install {LIB_NAME}'."
-                )
-
+                raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=LIB_NAME))
             api = HfApi()
 
             repo_id = repo_name.split("/")[1]
@@ -1470,9 +1021,8 @@ def upload_folder_to_hub(
                 dataset_module = importlib.import_module(LIB_NAME)
                 push = getattr(dataset_module, "push")
             else:
-                raise ModuleNotFoundError(
-                    f"The '{LIB_NAME}' package is not installed. Please install it using 'pip install {LIB_NAME}'."
-                )
+                raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=LIB_NAME))
+
             meta_path = os.path.join(folder_path, "meta.json")
             with open(meta_path, "r") as meta_file:
                 meta_data = json.load(meta_file)
@@ -1523,9 +1073,7 @@ def upload_file_to_hub(
             None
         """
         if token is None:
-            raise ValueError(
-                "A valid token is required for Hugging Face Hub authentication."
-            )
+            raise ValueError(Errors.E022)
         subprocess.run(["huggingface-cli", "login", "--token", token], check=True)
 
         file_extension = file_path.split(".")[-1]
@@ -1536,9 +1084,7 @@ def upload_file_to_hub(
                 huggingface_hub = importlib.import_module(LIB_NAME)
                 HfApi = getattr(huggingface_hub, "HfApi")
             else:
-                raise ModuleNotFoundError(
-                    f"The '{LIB_NAME}' package is not installed. Please install it using 'pip install {LIB_NAME}'."
-                )
+                raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=LIB_NAME))
 
             api = HfApi()
             repo_id = repo_name.split("/")[1]
@@ -1559,9 +1105,7 @@ def upload_file_to_hub(
                 Dataset = getattr(dataset_module, "Dataset")
 
             else:
-                raise ModuleNotFoundError(
-                    f"The '{LIB_NAME}' package is not installed. Please install it using 'pip install {LIB_NAME}'."
-                )
+                raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=LIB_NAME))
 
             with open(file_path, "r", encoding="utf-8") as f:
                 lines = f.readlines()
diff --git a/langtest/metrics/embedding_distance.py b/langtest/metrics/embedding_distance.py
index 4df9dc723..de3e27d01 100644
--- a/langtest/metrics/embedding_distance.py
+++ b/langtest/metrics/embedding_distance.py
@@ -1,6 +1,7 @@
 import numpy as np
 from typing import Callable
 import functools
+from ..errors import Errors
 
 
 class EmbeddingDistance:
@@ -25,9 +26,7 @@ def validate_input(func: Callable[[np.ndarray, np.ndarray], np.ndarray]):
         @functools.wraps(func)
         def wrapper(a: np.ndarray, b: np.ndarray) -> np.ndarray:
             if not isinstance(a, np.ndarray) or not isinstance(b, np.ndarray):
-                raise ValueError(
-                    f"Input arrays must be of type 'np.ndarray', but received types: {type(a)} and {type(b)}"
-                )
+                raise ValueError(Errors.E033.format(type_a=type(a), type_b=type(b)))
             return func(a, b)
 
         return wrapper
@@ -36,7 +35,7 @@ def __getitem__(self, name):
         if name in self.available_embedding_distance:
             return self.available_embedding_distance[name]
         else:
-            raise KeyError(f"Distance function '{name}' not found")
+            raise KeyError(Errors.E34.format(name=name))
 
     @staticmethod
     @validate_input
diff --git a/langtest/metrics/string_distance.py b/langtest/metrics/string_distance.py
index 78a409bc1..bd4599895 100644
--- a/langtest/metrics/string_distance.py
+++ b/langtest/metrics/string_distance.py
@@ -1,5 +1,6 @@
 from typing import Callable
 import functools
+from ..errors import Errors
 
 
 class StringDistance:
@@ -23,9 +24,7 @@ def validate_input(func: Callable[[str, str], float]):
         @functools.wraps(func)
         def wrapper(str1: str, str2: str) -> float:
             if not isinstance(str1, str) or not isinstance(str2, str):
-                raise ValueError(
-                    f"Input strings must be of type 'str', but received types: {type(str1)} and {type(str2)}"
-                )
+                raise ValueError(Errors.E035.format(type_a=type(str1), type_b=str))
             return func(str1, str2)
 
         return wrapper
@@ -34,7 +33,7 @@ def __getitem__(self, name):
         if name in self.available_string_distance:
             return self.available_string_distance[name]
         else:
-            raise KeyError(f"String distance function '{name}' not found")
+            raise KeyError(Errors.E036.format(name=name))
 
     @staticmethod
     @validate_input
diff --git a/langtest/modelhandler/__init__.py b/langtest/modelhandler/__init__.py
index 28679f869..5c6fb203f 100644
--- a/langtest/modelhandler/__init__.py
+++ b/langtest/modelhandler/__init__.py
@@ -1,1 +1,30 @@
-from .modelhandler import ModelFactory, LANGCHAIN_HUBS
+import importlib
+from .modelhandler import ModelAPI
+
+RENAME_HUBS = {
+    "azureopenai": "azure-openai",
+    "huggingfacehub": "huggingface-inference-api",
+}
+
+
+if importlib.util.find_spec("johnsnowlabs"):
+    import langtest.modelhandler.jsl_modelhandler
+
+if importlib.util.find_spec("transformers"):
+    import langtest.modelhandler.transformers_modelhandler
+
+if importlib.util.find_spec("spacy"):
+    import langtest.modelhandler.spacy_modelhandler
+
+if importlib.util.find_spec("langchain"):
+    import langchain
+    import langtest.modelhandler.llm_modelhandler
+
+    LANGCHAIN_HUBS = {
+        RENAME_HUBS.get(hub.lower(), hub.lower())
+        if hub.lower() in RENAME_HUBS
+        else hub.lower(): hub
+        for hub in langchain.llms.__all__
+    }
+else:
+    LANGCHAIN_HUBS = {}
diff --git a/langtest/modelhandler/custom_modelhandler.py b/langtest/modelhandler/custom_modelhandler.py
index 30439bc88..42575f1fd 100644
--- a/langtest/modelhandler/custom_modelhandler.py
+++ b/langtest/modelhandler/custom_modelhandler.py
@@ -2,15 +2,31 @@
 from langtest.utils.custom_types.output import SequenceClassificationOutput
 
 from langtest.utils.custom_types.predictions import SequenceLabel
-from .modelhandler import _ModelHandler
+from .modelhandler import ModelAPI
 from abc import ABC, abstractmethod
+import logging
+from ..errors import Errors
+
+logger = logging.getLogger(__name__)
 
 
 class PretrainedCustomModel(ABC):
+    """
+    Abstract base class for a custom pretrained model.
+
+    Attributes:
+        model (Any): The pretrained model to be used for prediction.
+
+    Methods:
+        load_model(cls, model: Any) -> "Any": Loads the pretrained model.
+        predict(self, text: str, *args, **kwargs): Predicts the output for the given input text.
+        __call__(self, text: str) -> None: Calls the predict method for the given input text.
+    """
+
     def __init__(self, model: Any) -> None:
         self.model = model
         if not hasattr(self.model, "predict"):
-            raise ValueError("Model must have a predict method")
+            raise ValueError(Errors.E037)
 
     @classmethod
     def load_model(cls, model: Any) -> "Any":
@@ -21,13 +37,28 @@ def predict(self, text: str, *args, **kwargs):
         try:
             return self.model.predict(text, *args, **kwargs)
         except Exception as e:
+            logger.error(e)
             raise e
 
     def __call__(self, text: str) -> None:
         return self.predict(text=text)
 
 
-class PretrainedModelForTextClassification(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForTextClassification(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for text classification using a pre-trained model.
+
+    Args:
+        PretrainedCustomModel: A class for loading a pre-trained custom model.
+        ModelAPI: A class for handling the model.
+
+    Methods:
+        predict: Predicts the class label for a given text input.
+
+    Returns:
+        SequenceClassificationOutput: A class containing the predicted class label and score.
+    """
+
     def predict(self, text: str, *args, **kwargs) -> SequenceClassificationOutput:
         try:
             out = self.model.predict(text, *args, **kwargs)
@@ -37,10 +68,27 @@ def predict(self, text: str, *args, **kwargs) -> SequenceClassificationOutput:
                 predictions=[SequenceLabel(label=elt, score=1) for elt in out]
             )
         except Exception as e:
+            logger.error(e)
             raise e
 
 
-class PretrainedModelForQA(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForQA(PretrainedCustomModel, ModelAPI):
+    """
+    A class for handling a pre-trained model for question answering.
+
+    Inherits from PretrainedCustomModel and ModelAPI.
+
+    Methods
+    -------
+    predict(text: str, *args, **kwargs)
+        Predicts the answer to a given question based on the pre-trained model.
+
+    Raises
+    ------
+    Exception
+        If an error occurs during prediction.
+    """
+
     def predict(self, text: str, *args, **kwargs):
         try:
             out = self.model.predict(text, *args, **kwargs)
@@ -49,45 +97,93 @@ def predict(self, text: str, *args, **kwargs):
             raise e
 
 
-class PretrainedModelForCrowsPairs(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForCrowsPairs(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for crows pairs.
+    """
+
     pass
 
 
-class PretrainedModelForWinoBias(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForWinoBias(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for wino bias.
+    """
+
     pass
 
 
-class PretrainedModelForTranslation(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForTranslation(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for translation.
+    """
+
     pass
 
 
-class PretrainedModelForSummarization(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForSummarization(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for summarization.
+    """
+
     pass
 
 
-class PretrainedModelForToxicity(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForToxicity(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for toxicity.
+    """
+
     pass
 
 
-class PretrainedModelForSecurity(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForSecurity(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for security.
+    """
+
     pass
 
 
-class PretrainedModelForPolitical(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForPolitical(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for political.
+    """
+
     pass
 
 
-class PretrainedModelForDisinformationTest(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForDisinformationTest(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for pretrained models that are used for disinformation.
+    """
+
     pass
 
 
-class PretrainedModelForFactualityTest(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForFactualityTest(PretrainedCustomModel, ModelAPI):
+    """
+    A class representing a pretrained model for factuality test.
+
+    Inherits from PretrainedCustomModel and ModelAPI.
+    """
+
     pass
 
 
-class PretrainedModelForSensitivityTest(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForSensitivityTest(PretrainedCustomModel, ModelAPI):
+    """
+    A class representing a pre-trained model for sensitivity testing.
+    Inherits from PretrainedCustomModel and ModelAPI.
+    """
+
     pass
 
 
-class PretrainedModelForSycophancyTest(PretrainedCustomModel, _ModelHandler):
+class PretrainedModelForSycophancyTest(PretrainedCustomModel, ModelAPI):
+    """
+    A custom model handler for testing sycophancy using a pre-trained model.
+    Inherits from PretrainedCustomModel and ModelAPI.
+    """
+
     pass
diff --git a/langtest/modelhandler/jsl_modelhandler.py b/langtest/modelhandler/jsl_modelhandler.py
index 3a9ff6be1..8ca93a555 100644
--- a/langtest/modelhandler/jsl_modelhandler.py
+++ b/langtest/modelhandler/jsl_modelhandler.py
@@ -4,9 +4,10 @@
 
 from langtest.utils.custom_types.output import TranslationOutput
 
-from .modelhandler import _ModelHandler
+from ..modelhandler import ModelAPI
 from ..utils.custom_types import NEROutput, NERPrediction, SequenceClassificationOutput
 from ..utils.lib_manager import try_import_lib
+from ..errors import Errors
 
 if try_import_lib("pyspark"):
     from pyspark.ml import PipelineModel
@@ -133,6 +134,8 @@ class PretrainedJSLModel(ABC):
             Loaded SparkNLP LightPipeline for inference.
     """
 
+    hub = "johnsnowlabs"
+
     @abstractmethod
     def __init__(
         self,
@@ -162,11 +165,7 @@ def __init__(
             self.model = _pipeline.fit(tmp_df)
 
         else:
-            raise ValueError(
-                f"Invalid SparkNLP model object: {type(model)}. "
-                f"John Snow Labs model handler accepts: "
-                f"[NLUPipeline, PretrainedPipeline, PipelineModel, LightPipeline]"
-            )
+            raise ValueError(Errors.E038.format(model_type=type(model)))
 
     @classmethod
     def load_model(cls, path) -> "NLUPipeline":
@@ -184,12 +183,9 @@ def load_model(cls, path) -> "NLUPipeline":
             if try_import_lib("johnsnowlabs"):
                 loaded_model = nlp.load(path)
             else:
-                raise ValueError(
-                    "johnsnowlabs is not installed. "
-                    "In order to use NLP Models Hub, johnsnowlabs should be installed!"
-                )
+                raise ValueError(Errors.E039)
 
-        return loaded_model
+        return cls(loaded_model)
 
     @abstractmethod
     def predict(self, text: str, *args, **kwargs) -> Any:
@@ -207,9 +203,11 @@ def __call__(
         return self.predict(text=text)
 
 
-class PretrainedModelForNER(PretrainedJSLModel, _ModelHandler):
+class PretrainedModelForNER(PretrainedJSLModel, ModelAPI):
     """Pretrained model for NER tasks."""
 
+    task = "ner"
+
     def __init__(
         self,
         model: Union[
@@ -232,9 +230,7 @@ def __init__(
                 break
 
         if ner_model is None:
-            raise ValueError(
-                "Invalid PipelineModel! There should be at least one NER component."
-            )
+            raise ValueError(Errors.E040.format(var="NER"))
 
         self.output_col = ner_model.getOutputCol()
 
@@ -387,9 +383,11 @@ def is_ner_annotator(model_instance) -> bool:
         return False
 
 
-class PretrainedModelForTextClassification(PretrainedJSLModel, _ModelHandler):
+class PretrainedModelForTextClassification(PretrainedJSLModel, ModelAPI):
     """Pretrained model for text classification tasks"""
 
+    task = "text-classification"
+
     def __init__(
         self,
         model: Union[
@@ -411,9 +409,7 @@ def __init__(
                 break
 
         if _classifier is None:
-            raise ValueError(
-                "Invalid PipelineModel! There should be at least one classifier component."
-            )
+            raise ValueError(Errors.E040.format(var="classifier"))
 
         self.output_col = _classifier.getOutputCol()
         self.classes = _classifier.getClasses()
@@ -466,9 +462,11 @@ def predict_raw(self, text: str) -> List[str]:
         return [x["label"] for x in prediction]
 
 
-class PretrainedModelForTranslation(PretrainedJSLModel, _ModelHandler):
+class PretrainedModelForTranslation(PretrainedJSLModel, ModelAPI):
     """Pretrained model for translations tasks"""
 
+    task = "translation"
+
     def __init__(
         self,
         model: Union[
@@ -490,9 +488,7 @@ def __init__(
                 break
 
         if _translator is None:
-            raise ValueError(
-                "Invalid PipelineModel! There should be at least one translator component."
-            )
+            raise ValueError(Errors.E040.format(var="translator"))
 
         self.output_col = _translator.getOutputCol()
         self.model = LightPipeline(self.model)
diff --git a/langtest/modelhandler/llm_modelhandler.py b/langtest/modelhandler/llm_modelhandler.py
index 0784b6fa9..a0c0af8b5 100644
--- a/langtest/modelhandler/llm_modelhandler.py
+++ b/langtest/modelhandler/llm_modelhandler.py
@@ -1,9 +1,10 @@
 import inspect
-from typing import Union
+from typing import Any, Union
 import langchain.llms as lc
 from langchain import LLMChain, PromptTemplate
 from pydantic import ValidationError
-from ..modelhandler.modelhandler import _ModelHandler, LANGCHAIN_HUBS
+from ..modelhandler.modelhandler import ModelAPI, LANGCHAIN_HUBS
+from ..errors import Errors
 
 from ..metrics import EmbeddingDistance
 from langchain import OpenAI
@@ -11,7 +12,7 @@
 from langtest.transform.utils import compare_generations_overlap
 
 
-class PretrainedModelForQA(_ModelHandler):
+class PretrainedModelForQA(ModelAPI):
     """A class representing a pretrained model for question answering.
 
     Attributes:
@@ -24,7 +25,7 @@ class PretrainedModelForQA(_ModelHandler):
         ConfigError: If there is an error in the model configuration.
     """
 
-    def __init__(self, hub: str, model: str, *args, **kwargs):
+    def __init__(self, hub: str, model: Any, *args, **kwargs):
         """Constructor class
 
         Args:
@@ -36,6 +37,8 @@ def __init__(self, hub: str, model: str, *args, **kwargs):
         self.model = model
         self.hub = LANGCHAIN_HUBS[hub]
         self.kwargs = kwargs
+        if isinstance(model, str):
+            self.model = self.load_model(hub, model, *args, **kwargs).model
 
     @classmethod
     def load_model(cls, hub: str, path: str, *args, **kwargs) -> "PretrainedModelForQA":
@@ -65,16 +68,17 @@ def load_model(cls, hub: str, path: str, *args, **kwargs) -> "PretrainedModelFor
                 cls.model = model(model_id=path, *args, **kwargs)
             elif "repo_id" in default_args:
                 cls.model = model(repo_id=path, model_kwargs=kwargs)
-            return cls.model
+
+            return cls(hub, cls.model, *args, **kwargs)
+
         except ImportError:
-            raise ValueError(
-                f"""Model "{path}" is not found online or local.
-                Please install langchain by pip install langchain"""
-            )
+            raise ValueError(Errors.E044.format(path=path))
         except ValidationError as e:
             error_msg = [err["loc"][0] for err in e.errors()]
             raise ConfigError(
-                f"\nPlease update model_parameters section in config.yml file for {path} model in {hub}.\nmodel_parameters:\n\t{error_msg[0]}: value \n\n{error_msg} is required field(s), please provide them in config.yml "
+                Errors.E045.format(
+                    path=path, hub=hub, field=error_msg[0], required_fields=error_msg
+                )
             )
 
     def predict(self, text: Union[str, dict], prompt: dict, *args, **kwargs):
@@ -148,7 +152,7 @@ def __str__(self):
         return self.message
 
 
-class PretrainedModelForSummarization(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForSummarization(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for summarization.
 
     Inherits:
@@ -158,7 +162,7 @@ class PretrainedModelForSummarization(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForToxicity(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForToxicity(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for toxicity detection.
 
     Inherits:
@@ -168,7 +172,7 @@ class PretrainedModelForToxicity(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForSecurity(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForSecurity(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for security detection.
 
     Inherits:
@@ -178,7 +182,7 @@ class PretrainedModelForSecurity(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForClinicalTests(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForClinicalTests(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for security detection.
 
     Inherits:
@@ -188,7 +192,7 @@ class PretrainedModelForClinicalTests(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForDisinformationTest(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForDisinformationTest(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for disinformation test.
     Inherits:
         PretrainedModelForQA: The base class for pretrained models.
@@ -197,7 +201,7 @@ class PretrainedModelForDisinformationTest(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForPolitical(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForPolitical(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for security detection.
 
     Inherits:
@@ -207,7 +211,7 @@ class PretrainedModelForPolitical(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForSensitivityTest(_ModelHandler):
+class PretrainedModelForSensitivityTest(ModelAPI):
     """
     A class for sensitivity testing using a pretrained model and embeddings.
 
@@ -256,19 +260,21 @@ def load_model(cls, path: str, *args, **kwargs) -> tuple:
             ValueError: If the 'OPENAI_API_KEY' environment variable is not set.
         """
         try:
-            from ..embeddings.openai import OpenaiEmbeddings
-
-            llm = OpenAI(
-                model_name=path,
-                temperature=0,
-                openai_api_key=os.environ["OPENAI_API_KEY"],
-                *args,
-                **kwargs,
-            )
-            embeddings_model = OpenaiEmbeddings(model="text-embedding-ada-002")
-            return llm, embeddings_model
+            if isinstance(path, str):
+                from ..embeddings.openai import OpenaiEmbeddings
+
+                llm = OpenAI(
+                    model_name=path,
+                    temperature=0,
+                    openai_api_key=os.environ["OPENAI_API_KEY"],
+                    *args,
+                    **kwargs,
+                )
+                embeddings_model = OpenaiEmbeddings(model="text-embedding-ada-002")
+                return cls((llm, embeddings_model))
+            return cls(path)
         except KeyError:
-            raise ValueError("The 'OPENAI_API_KEY' environment variable is not set.")
+            raise ValueError(Errors.E032)
 
     def predict(self, text: str, text_transformed: str, test_name: str, **kwargs):
         """
@@ -324,7 +330,7 @@ def __call__(self, text: str, text_transformed: str, test_name: str, **kwargs):
         )
 
 
-class PretrainedModelForWinoBias(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForWinoBias(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for wino-bias detection.
 
     Inherits:
@@ -334,7 +340,7 @@ class PretrainedModelForWinoBias(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForLegal(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForLegal(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for legal-tests.
 
     Inherits:
@@ -344,7 +350,7 @@ class PretrainedModelForLegal(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForFactualityTest(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForFactualityTest(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for factuality detection.
 
     Inherits:
@@ -354,7 +360,7 @@ class PretrainedModelForFactualityTest(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForSycophancyTest(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForSycophancyTest(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for sycophancy test.
 
     This class inherits from PretrainedModelForQA and provides functionality
diff --git a/langtest/modelhandler/modelhandler.py b/langtest/modelhandler/modelhandler.py
index 439ea1895..b4d265e8a 100644
--- a/langtest/modelhandler/modelhandler.py
+++ b/langtest/modelhandler/modelhandler.py
@@ -1,17 +1,19 @@
-import importlib
 from abc import ABC, abstractmethod
-from typing import List, Union
+from collections import defaultdict
+from typing import Union
 
 from langtest.utils.lib_manager import try_import_lib
-from ..utils.custom_types import NEROutput, SequenceClassificationOutput
 
 RENAME_HUBS = {
     "azureopenai": "azure-openai",
     "huggingfacehub": "huggingface-inference-api",
+    "transformers": "huggingface",
+    "jsl": "johnsnowlabs",
 }
 
 if try_import_lib("langchain"):
     import langchain
+    import langchain.llms
 
     LANGCHAIN_HUBS = {
         RENAME_HUBS.get(hub.lower(), hub.lower())
@@ -23,12 +25,14 @@
     LANGCHAIN_HUBS = {}
 
 
-class _ModelHandler(ABC):
+class ModelAPI(ABC):
     """Abstract base class for handling different models.
 
     Implementations should inherit from this class and override load_model() and predict() methods.
     """
 
+    model_registry = defaultdict(lambda: defaultdict(lambda: ModelAPI))
+
     @abstractmethod
     def load_model(cls, *args, **kwargs):
         """Load the model."""
@@ -39,353 +43,10 @@ def predict(self, text: Union[str, dict], *args, **kwargs):
         """Perform predictions on input text."""
         raise NotImplementedError()
 
-
-class ModelFactory:
-    """A factory class for instantiating models."""
-
-    SUPPORTED_TASKS = [
-        "ner",
-        "text-classification",
-        "question-answering",
-        "summarization",
-        "toxicity",
-        "translation",
-        "security",
-        "clinical-tests",
-        "disinformation-test",
-        "political",
-        "sensitivity-test",
-        "wino-bias",
-        "legal-tests",
-        "factuality-test",
-        "sycophancy-test",
-        "crows-pairs",
-        "stereoset",
-    ]
-    SUPPORTED_MODULES = [
-        "pyspark",
-        "sparknlp",
-        "nlu",
-        "transformers",
-        "spacy",
-        "langchain",
-    ]
-    SUPPORTED_HUBS = [
-        "spacy",
-        "huggingface",
-        "johnsnowlabs",
-        "openai",
-        "cohere",
-        "ai21",
-        "custom",
-    ] + list(LANGCHAIN_HUBS.keys())
-
-    def __init__(self, model: str, task: str, hub: str, *args, **kwargs):
-        """Initializes the ModelFactory object.
-
-        Args:
-            model (str):
-                path to the model to evaluate
-            task (str):
-                task to perform
-
-        Raises:
-            ValueError: If the task specified is not supported.
-            ValueError: If the given model is not supported.
-        """
-        assert task in self.SUPPORTED_TASKS, ValueError(
-            f"Task '{task}' not supported. Please choose one of: {', '.join(self.SUPPORTED_TASKS)}"
-        )
-        if isinstance(model, tuple):
-            module_name = model[0].__module__.split(".")[0]
-        else:
-            module_name = model.__module__.split(".")[0]
-
-        if hub != "custom":
-            assert module_name in self.SUPPORTED_MODULES, ValueError(
-                f"Module '{module_name}' is not supported. "
-                f"Please choose one of: {', '.join(self.SUPPORTED_MODULES)}"
-            )
-
-        if module_name in ["pyspark", "sparknlp", "nlu"]:
-            model_handler = importlib.import_module(
-                "langtest.modelhandler.jsl_modelhandler"
-            )
-
-        elif module_name == "langchain":
-            model_handler = importlib.import_module(
-                "langtest.modelhandler.llm_modelhandler"
-            )
-
-        elif hub == "custom":
-            model_handler = importlib.import_module(
-                "langtest.modelhandler.custom_modelhandler"
-            )
-        else:
-            model_handler = importlib.import_module(
-                f"langtest.modelhandler.{module_name}_modelhandler"
-            )
-
-        if task == "ner":
-            self.model_class = model_handler.PretrainedModelForNER(model)
-        elif task in ("question-answering"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForQA(
-                hub, model, *args, **kwargs
-            )
-        elif task in ("summarization"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForSummarization(
-                hub, model, *args, **kwargs
-            )
-        elif task in ("toxicity"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForToxicity(
-                hub, model, *args, **kwargs
-            )
-
-        elif task in ("clinical-tests"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForClinicalTests(
-                hub, model, *args, **kwargs
-            )
-
-        elif task in ("legal-tests"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForLegal(
-                hub, model, *args, **kwargs
-            )
-
-        elif task in ("disinformation-test"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForDisinformationTest(
-                hub, model, *args, **kwargs
-            )
-
-        elif task == "political":
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForPolitical(
-                hub, model, *args, **kwargs
-            )
-
-        elif task == "translation":
-            self.model_class = model_handler.PretrainedModelForTranslation(model)
-
-        elif task == "security":
-            self.model_class = model_handler.PretrainedModelForSecurity(
-                hub, model, *args, **kwargs
-            )
-        elif task == "sensitivity-test":
-            self.model_class = model_handler.PretrainedModelForSensitivityTest(model)
-
-        elif task == "wino-bias":
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForWinoBias(
-                hub=hub, model=model, *args, **kwargs
-            )
-
-        elif task == "crows-pairs":
-            self.model_class = model_handler.PretrainedModelForCrowsPairs(model)
-        elif task == "stereoset":
-            self.model_class = model_handler.PretrainedModelForStereoSet(model)
-
-        elif task == "factuality-test":
-            self.model_class = model_handler.PretrainedModelForFactualityTest(
-                hub, model, *args, **kwargs
-            )
-
-        elif task in ("sycophancy-test"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            self.model_class = model_handler.PretrainedModelForSycophancyTest(
-                hub, model, *args, **kwargs
-            )
-        else:
-            self.model_class = model_handler.PretrainedModelForTextClassification(model)
-
-    @classmethod
-    def load_model(
-        cls, task: str, hub: str, path: str, *args, **kwargs
-    ) -> "ModelFactory":
-        """Loads the model.
-
-        Args:
-            path (str):
-                path to model to use
-            task (str):
-                task to perform
-            hub (str):
-                model hub to load custom model from the path, either to hub or local disk.
-
-        Returns:
-            ModelHandler:
-        """
-        assert task in cls.SUPPORTED_TASKS, ValueError(
-            f"Task '{task}' not supported. Please choose one of: {', '.join(cls.SUPPORTED_TASKS)}"
-        )
-
-        assert hub != "custom" and hub in cls.SUPPORTED_HUBS, ValueError(
-            f"Invalid 'hub' parameter. Supported hubs are: {', '.join(cls.SUPPORTED_HUBS)}"
-        )
-
-        if hub == "johnsnowlabs":
-            if importlib.util.find_spec("johnsnowlabs"):
-                modelhandler_module = importlib.import_module(
-                    "langtest.modelhandler.jsl_modelhandler"
-                )
-            else:
-                raise ModuleNotFoundError(
-                    """Please install the johnsnowlabs library by calling `pip install johnsnowlabs`.
-                For in-depth instructions, head-over to https://nlu.johnsnowlabs.com/docs/en/install"""
-                )
-
-        elif hub == "huggingface":
-            if importlib.util.find_spec("transformers"):
-                modelhandler_module = importlib.import_module(
-                    "langtest.modelhandler.transformers_modelhandler"
-                )
-            else:
-                raise ModuleNotFoundError(
-                    """Please install the transformers library by calling `pip install transformers`.
-                For in-depth instructions, head-over to https://huggingface.co/docs/transformers/installation"""
-                )
-
-        elif hub == "spacy":
-            if importlib.util.find_spec("spacy"):
-                modelhandler_module = importlib.import_module(
-                    f"langtest.modelhandler.{hub}_modelhandler"
-                )
-            else:
-                raise ModuleNotFoundError(
-                    """Please install the spacy library by calling `pip install spacy`.
-                For in-depth instructions, head-over to https://spacy.io/usage"""
-                )
-        elif hub == "custom":
-            modelhandler_module = importlib.import_module(
-                "langtest.modelhandler.custom_modelhandler"
-            )
-
-        elif hub.lower() in LANGCHAIN_HUBS:
-            modelhandler_module = importlib.import_module(
-                "langtest.modelhandler.llm_modelhandler"
-            )
-
-        if task == "ner":
-            model_class = modelhandler_module.PretrainedModelForNER.load_model(path)
-        elif task in ("question-answering"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            model_class = modelhandler_module.PretrainedModelForQA.load_model(
-                hub, path, *args, **kwargs
-            )
-        elif task in ("summarization"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            model_class = modelhandler_module.PretrainedModelForSummarization.load_model(
-                hub, path, *args, **kwargs
-            )
-        elif task in ("toxicity"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            model_class = modelhandler_module.PretrainedModelForToxicity.load_model(
-                hub, path, *args, **kwargs
-            )
-        elif task == "translation":
-            model_class = modelhandler_module.PretrainedModelForTranslation.load_model(
-                path
-            )
-
-        elif task == "security":
-            model_class = modelhandler_module.PretrainedModelForSecurity.load_model(
-                hub, path, *args, **kwargs
-            )
-
-        elif task == "clinical-tests":
-            model_class = modelhandler_module.PretrainedModelForClinicalTests.load_model(
-                hub, path, *args, **kwargs
-            )
-        elif task == "legal-tests":
-            model_class = modelhandler_module.PretrainedModelForLegal.load_model(
-                hub, path, *args, **kwargs
-            )
-
-        elif task == "political":
-            model_class = modelhandler_module.PretrainedModelForPolitical.load_model(
-                hub, path, *args, **kwargs
-            )
-
-        elif task in ("disinformation-test"):
-            model_class = (
-                modelhandler_module.PretrainedModelForDisinformationTest.load_model(
-                    hub, path, *args, **kwargs
-                )
-            )
-
-        elif task == "sensitivity-test":
-            model_class = (
-                modelhandler_module.PretrainedModelForSensitivityTest.load_model(path)
-            )
-        elif task in ("wino-bias"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            model_class = modelhandler_module.PretrainedModelForWinoBias.load_model(
-                hub=hub, path=path, *args, **kwargs
-            )
-
-        elif task in ("crows-pairs"):
-            model_class = modelhandler_module.PretrainedModelForCrowsPairs.load_model(
-                path
-            )
-
-        elif task in ("stereoset"):
-            model_class = modelhandler_module.PretrainedModelForStereoSet.load_model(path)
-
-        elif task in ("factuality-test"):
-            model_class = modelhandler_module.PretrainedModelForFactualityTest.load_model(
-                hub, path, *args, **kwargs
-            )
-        elif task in ("sycophancy-test"):
-            _ = kwargs.pop("user_prompt") if "user_prompt" in kwargs else kwargs
-            model_class = modelhandler_module.PretrainedModelForSycophancyTest.load_model(
-                hub, path, *args, **kwargs
-            )
-        else:
-            model_class = (
-                modelhandler_module.PretrainedModelForTextClassification.load_model(path)
-            )
-
-        return cls(model_class, task, hub, *args, **kwargs)
-
-    def predict(
-        self, text: Union[str, dict], **kwargs
-    ) -> Union[NEROutput, SequenceClassificationOutput]:
-        """Perform predictions on input text.
-
-        Args:
-            text (str): Input text to perform predictions on.
-
-        Returns:
-            Union[NEROutput, SequenceClassificationOutput]:
-                predicted output
-        """
-        return self.model_class(text=text, **kwargs)
-
-    def predict_raw(self, text: str) -> List[str]:
-        """Perform predictions on input text.
-
-        Args:
-            text (str): Input text to perform predictions on.
-
-        Returns:
-            List[str]: Predictions.
-        """
-        return self.model_class.predict_raw(text)
-
-    def __call__(
-        self, text: Union[str, dict], *args, **kwargs
-    ) -> Union[NEROutput, SequenceClassificationOutput]:
-        """Alias of the 'predict' method
-
-        Args:
-            text (str): Input text to perform predictions on.
-
-        Returns:
-            Union[NEROutput, SequenceClassificationOutput]:
-                predicted output
-        """
-        return self.model_class(text=text, **kwargs)
+    def __init_subclass__(cls, *args, **kwargs) -> None:
+        hub = cls.__module__.split(".")[-1].split("_")[0]
+        if hub in RENAME_HUBS:
+            hub = RENAME_HUBS[hub]
+        task = cls.__name__.replace("PretrainedModelFor", "").lower()
+        ModelAPI.model_registry[hub][task] = cls
+        return super().__init_subclass__(*args, **kwargs)
diff --git a/langtest/modelhandler/spacy_modelhandler.py b/langtest/modelhandler/spacy_modelhandler.py
index d2a2f8e48..58e017452 100644
--- a/langtest/modelhandler/spacy_modelhandler.py
+++ b/langtest/modelhandler/spacy_modelhandler.py
@@ -3,11 +3,13 @@
 import spacy
 from spacy.tokens import Doc
 
-from .modelhandler import _ModelHandler
+from .modelhandler import ModelAPI
 from ..utils.custom_types import NEROutput, NERPrediction, SequenceClassificationOutput
+from pkg_resources import resource_filename
+from ..errors import Errors
 
 
-class PretrainedModelForNER(_ModelHandler):
+class PretrainedModelForNER(ModelAPI):
     """SpaCy pretrained model for NER tasks
 
     Args:
@@ -36,11 +38,9 @@ def load_model(cls, path: str):
             path (str): name of path to model to load
         """
         try:
-            return spacy.load(path)
+            return cls(spacy.load(path))
         except OSError:
-            raise ValueError(
-                f"""Model "{path}" is not found online or local. Please install it by python -m spacy download {path} or check the path."""
-            )
+            raise ValueError(Errors.E041.format(path=path))
 
     def predict(self, text: str, *args, **kwargs) -> NEROutput:
         """Perform predictions on the input text.
@@ -90,7 +90,7 @@ def predict_raw(self, text: str) -> List[str]:
         ]
 
 
-class PretrainedModelForTextClassification(_ModelHandler):
+class PretrainedModelForTextClassification(ModelAPI):
     """SpaCy pretrained model for text classification tasks
 
     Args:
@@ -124,11 +124,11 @@ def load_model(cls, path: str):
             path (str): name of path to model to load
         """
         try:
-            return spacy.load(path)
+            if path == "textcat_imdb":
+                path = resource_filename("langtest", "data/textcat_imdb")
+            return cls(spacy.load(path))
         except OSError:
-            raise ValueError(
-                f"""Model "{path}" is not found online or local. Please install it by python -m spacy download {path} or check the path."""
-            )
+            raise ValueError(Errors.E041.format(path=path))
 
     def predict(
         self, text: str, return_all_scores: bool = False, *args, **kwargs
@@ -170,7 +170,7 @@ def __call__(
         return self.predict(text=text, return_all_scores=return_all_scores, **kwargs)
 
 
-class PretrainedModelForTranslation(_ModelHandler):
+class PretrainedModelForTranslation(ModelAPI):
     """SpaCy pretrained model for translation tasks
 
     Args:
@@ -199,11 +199,9 @@ def load_model(cls, path: str):
             path (str): name of path to model to load
         """
         try:
-            return spacy.load(path)
+            return cls(spacy.load(path))
         except OSError:
-            raise ValueError(
-                f"""Model "{path}" is not found online or local. Please install it by python -m spacy download {path} or check the path."""
-            )
+            raise ValueError(Errors.E041.format(path=path))
 
     def predict(self, text: str, *args, **kwargs) -> str:
         """Perform translation predictions on the input text.
diff --git a/langtest/modelhandler/transformers_modelhandler.py b/langtest/modelhandler/transformers_modelhandler.py
index 909f398f9..feeb2406e 100644
--- a/langtest/modelhandler/transformers_modelhandler.py
+++ b/langtest/modelhandler/transformers_modelhandler.py
@@ -3,7 +3,7 @@
 import numpy as np
 from transformers import Pipeline, pipeline, AutoModelForCausalLM, AutoTokenizer
 
-from .modelhandler import _ModelHandler
+from .modelhandler import ModelAPI
 from ..utils.custom_types import (
     NEROutput,
     NERPrediction,
@@ -13,9 +13,10 @@
 from langtest.utils.lib_manager import try_import_lib
 import importlib
 from langtest.transform.utils import compare_generations_overlap
+from ..errors import Errors
 
 
-class PretrainedModelForNER(_ModelHandler):
+class PretrainedModelForNER(ModelAPI):
     """Transformers pretrained model for NER tasks
 
     Args:
@@ -145,7 +146,9 @@ def load_model(cls, path: str) -> "Pipeline":
         Returns:
             'Pipeline':
         """
-        return pipeline(model=path, task="ner", ignore_labels=[])
+        if isinstance(path, str):
+            return cls(pipeline(model=path, task="ner", ignore_labels=[]))
+        return cls(path)
 
     def predict(self, text: str, **kwargs) -> NEROutput:
         """Perform predictions on the input text.
@@ -200,7 +203,7 @@ def __call__(self, text: str, *args, **kwargs) -> NEROutput:
         return self.predict(text=text, **kwargs)
 
 
-class PretrainedModelForTextClassification(_ModelHandler):
+class PretrainedModelForTextClassification(ModelAPI):
     """Transformers pretrained model for text classification tasks
 
     Attributes:
@@ -231,7 +234,9 @@ def labels(self) -> List[str]:
     @classmethod
     def load_model(cls, path: str) -> "Pipeline":
         """Load and return text classification transformers pipeline"""
-        return pipeline(model=path, task="text-classification")
+        if isinstance(path, str):
+            return cls(pipeline(model=path, task="text-classification"))
+        return cls(path)
 
     def predict(
         self,
@@ -282,7 +287,7 @@ def __call__(
         return self.predict(text=text, return_all_scores=return_all_scores, **kwargs)
 
 
-class PretrainedModelForTranslation(_ModelHandler):
+class PretrainedModelForTranslation(ModelAPI):
     """Transformers pretrained model for translation tasks
 
     Args:
@@ -302,7 +307,7 @@ def __init__(self, model):
         self.model = model
 
     @classmethod
-    def load_model(cls, path: str) -> "Pipeline":
+    def load_model(cls, path: str, *args, **kwargs) -> "Pipeline":
         """Load the Translation model into the `model` attribute.
 
         Args:
@@ -315,12 +320,12 @@ def load_model(cls, path: str) -> "Pipeline":
         from ..langtest import HARNESS_CONFIG as harness_config
 
         config = harness_config["model_parameters"]
-        tgt_lang = config.get("target_language")
+        tgt_lang = config.get("target_language") or kwargs.get("target_language")
 
         if "t5" in path:
-            return pipeline(f"translation_en_to_{tgt_lang}", model=path)
+            return cls(pipeline(f"translation_en_to_{tgt_lang}", model=path))
         else:
-            return pipeline(model=path, src_lang="en", tgt_lang=tgt_lang)
+            return cls(pipeline(model=path, src_lang="en", tgt_lang=tgt_lang))
 
     def predict(self, text: str, **kwargs) -> TranslationOutput:
         """Perform predictions on the input text.
@@ -341,7 +346,7 @@ def __call__(self, text: str, *args, **kwargs) -> TranslationOutput:
         return self.predict(text=text, **kwargs)
 
 
-class PretrainedModelForWinoBias(_ModelHandler):
+class PretrainedModelForWinoBias(ModelAPI):
     """A class representing a pretrained model for wino-bias detection.
 
     Args:
@@ -371,10 +376,11 @@ def load_model(cls, path: str, *args, **kwargs) -> "Pipeline":
         Returns:
             'Pipeline':
         """
+        if isinstance(path, str):
+            unmasker = pipeline("fill-mask", model=path)
+            return cls(unmasker)
 
-        unmasker = pipeline("fill-mask", model=path)
-
-        return unmasker
+        return cls(path)
 
     def predict(self, text: str, **kwargs) -> Dict:
         """Perform predictions on the input text.
@@ -419,7 +425,7 @@ def __call__(self, text: str, *args, **kwargs) -> Dict:
         return self.predict(text=text, **kwargs)
 
 
-class PretrainedModelForCrowsPairs(_ModelHandler):
+class PretrainedModelForCrowsPairs(ModelAPI):
     """A class representing a pretrained model for Crows-Pairs detection.
 
     Args:
@@ -450,7 +456,10 @@ def load_model(cls, path: str) -> "Pipeline":
             'Pipeline':
         """
 
-        return pipeline("fill-mask", model=path)
+        if isinstance(path, str):
+            return cls(pipeline("fill-mask", model=path))
+
+        return cls(path)
 
     def predict(self, text: str, **kwargs) -> Dict:
         """Perform predictions on the input text.
@@ -470,7 +479,7 @@ def __call__(self, text: str, *args, **kwargs) -> Dict:
         return self.predict(text=text, **kwargs)
 
 
-class PretrainedModelForStereoSet(_ModelHandler):
+class PretrainedModelForStereoSet(ModelAPI):
     """A class representing a pretrained model for StereoSet detection.
 
     Args:
@@ -497,11 +506,13 @@ def load_model(cls, path: str) -> "Pipeline":
         Returns:
             'Pipeline':
         """
-
-        return (
-            AutoModelForCausalLM.from_pretrained(path),
-            AutoTokenizer.from_pretrained(path),
-        )
+        if isinstance(path, str):
+            models = (
+                AutoModelForCausalLM.from_pretrained(path),
+                AutoTokenizer.from_pretrained(path),
+            )
+            return cls(models)
+        return cls(path)
 
     def predict(self, text: str, **kwargs) -> Dict:
         """Perform predictions on the input text.
@@ -526,14 +537,14 @@ def __call__(self, text: str, *args, **kwargs) -> Dict:
         return self.predict(text=text, **kwargs)
 
 
-class PretrainedModelForQA(_ModelHandler):
+class PretrainedModelForQA(ModelAPI):
     """Transformers pretrained model for QA tasks
 
     Args:
         model (transformers.pipeline.Pipeline): Pretrained HuggingFace QA pipeline for predictions.
     """
 
-    def __init__(self, hub, model, **kwargs):
+    def __init__(self, model, **kwargs):
         """Constructor method
 
         Args:
@@ -551,12 +562,10 @@ def _check_langchain_package(self):
             langchain = importlib.import_module(LIB_NAME)
             self.PromptTemplate = getattr(langchain, "PromptTemplate")
         else:
-            raise ModuleNotFoundError(
-                f"The '{LIB_NAME}' package is not installed. Please install it using 'pip install {LIB_NAME}'."
-            )
+            raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=LIB_NAME))
 
-    @staticmethod
-    def load_model(hub: str, path: str, **kwargs) -> "Pipeline":
+    @classmethod
+    def load_model(cls, path: str, **kwargs) -> "Pipeline":
         """Load the QA model into the `model` attribute.
 
         Args:
@@ -568,7 +577,7 @@ def load_model(hub: str, path: str, **kwargs) -> "Pipeline":
         """
         if "task" in kwargs.keys():
             kwargs.pop("task")
-        return pipeline(model=path, **kwargs)
+        return cls(pipeline(model=path, **kwargs))
 
     def predict(self, text: Union[str, dict], prompt: dict, **kwargs) -> str:
         """Perform predictions on the input text.
@@ -591,7 +600,7 @@ def __call__(self, text: Union[str, dict], prompt: dict, **kwargs) -> str:
         return self.predict(text=text, prompt=prompt, **kwargs)
 
 
-class PretrainedModelForSummarization(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForSummarization(PretrainedModelForQA, ModelAPI):
     """Transformers pretrained model for summarization tasks
 
     Args:
@@ -601,7 +610,7 @@ class PretrainedModelForSummarization(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForToxicity(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForToxicity(PretrainedModelForQA, ModelAPI):
     """Transformers pretrained model for summarization tasks
 
     Args:
@@ -611,7 +620,7 @@ class PretrainedModelForToxicity(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForSecurity(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForSecurity(PretrainedModelForQA, ModelAPI):
     """Transformers pretrained model for summarization tasks
 
     Args:
@@ -621,7 +630,7 @@ class PretrainedModelForSecurity(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForPolitical(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForPolitical(PretrainedModelForQA, ModelAPI):
     """Transformers pretrained model for summarization tasks
 
     Args:
@@ -631,7 +640,7 @@ class PretrainedModelForPolitical(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForDisinformationTest(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForDisinformationTest(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for disinformation test.
     Inherits:
         PretrainedModelForQA: The base class for pretrained models.
@@ -640,7 +649,7 @@ class PretrainedModelForDisinformationTest(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForFactualityTest(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForFactualityTest(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for factuality detection.
 
     Inherits:
@@ -650,7 +659,7 @@ class PretrainedModelForFactualityTest(PretrainedModelForQA, _ModelHandler):
     pass
 
 
-class PretrainedModelForSensitivityTest(_ModelHandler):
+class PretrainedModelForSensitivityTest(ModelAPI):
     """A class for handling a pretrained model for sensitivity testing.
 
     This class wraps a pretrained transformer model for performing sensitivity testing
@@ -694,10 +703,13 @@ def load_model(cls, path: str):
         Returns:
             tuple: A tuple containing the loaded model and tokenizer.
         """
-        from ..utils.hf_utils import get_model_n_tokenizer
 
-        model, tokenizer = get_model_n_tokenizer(model_name=path)
-        return model, tokenizer
+        if isinstance(path, str):
+            from ..utils.hf_utils import get_model_n_tokenizer
+
+            model = get_model_n_tokenizer(model_name=path)
+            return model
+        return cls(path)
 
     def predict(self, text: str, text_transformed: str, test_name: str, **kwargs):
         """Perform predictions on the input text.
@@ -752,7 +764,7 @@ def __call__(self, text: str, text_transformed: str, test_name: str, **kwargs):
         return self.predict(text=text, text_transformed=text_transformed, **kwargs)
 
 
-class PretrainedModelForSycophancyTest(PretrainedModelForQA, _ModelHandler):
+class PretrainedModelForSycophancyTest(PretrainedModelForQA, ModelAPI):
     """A class representing a pretrained model for SycophancyTest
 
     Inherits:
diff --git a/langtest/pipelines/transformers/ner_pipeline.py b/langtest/pipelines/transformers/ner_pipeline.py
index b249be34b..4f08af262 100644
--- a/langtest/pipelines/transformers/ner_pipeline.py
+++ b/langtest/pipelines/transformers/ner_pipeline.py
@@ -13,6 +13,8 @@
 from langtest.datahandler.datasource import DataFactory
 from langtest.pipelines.utils.data_helpers.ner_dataset import NERDataset
 from langtest.pipelines.utils.metrics import compute_ner_metrics
+from langtest.tasks import TaskManager
+from langtest.errors import Warnings
 
 
 class NEREnd2EndPipeline(FlowSpec):
@@ -85,10 +87,10 @@ def setup(self):
         self.output_dir = "checkpoints/"
 
         self.train_datasource = DataFactory(
-            file_path={"data_source": self.train_data}, task=self.task
+            file_path={"data_source": self.train_data}, task=TaskManager(self.task)
         )
         self.eval_datasource = DataFactory(
-            file_path={"data_source": self.eval_data}, task=self.task
+            file_path={"data_source": self.eval_data}, task=TaskManager(self.task)
         )
 
         self.next(self.train)
@@ -187,7 +189,8 @@ def augment(self):
     def retrain(self):
         """Performs the training procedure using the augmented data created by langtest"""
         self.augmented_train_datasource = DataFactory(
-            file_path={"data_source": self.path_augmented_file}, task=self.task
+            file_path={"data_source": self.path_augmented_file},
+            task=TaskManager(self.task),
         )
         samples = self.augmented_train_datasource.load_raw()
 
@@ -251,7 +254,7 @@ def compare(self):
     @step
     def end(self):
         """Ending step of the flow (required by Metaflow)"""
-        logging.info(f"{self.__class__} successfully ran!")
+        logging.info(Warnings.W011.format(class_name=self.__class__))
 
 
 if __name__ == "__main__":
diff --git a/langtest/tasks/__init__.py b/langtest/tasks/__init__.py
new file mode 100644
index 000000000..32b1e4506
--- /dev/null
+++ b/langtest/tasks/__init__.py
@@ -0,0 +1,1 @@
+from .task import TaskManager
diff --git a/langtest/tasks/task.py b/langtest/tasks/task.py
new file mode 100644
index 000000000..8b4d80f09
--- /dev/null
+++ b/langtest/tasks/task.py
@@ -0,0 +1,750 @@
+import re
+from abc import ABC, abstractmethod
+from typing import Union
+from langtest.modelhandler import ModelAPI, LANGCHAIN_HUBS
+from langtest.errors import Errors, ColumnNameError
+
+from langtest.utils import custom_types as samples
+from langtest.utils.custom_types.predictions import NERPrediction
+
+
+class BaseTask(ABC):
+    """Abstract base class for all tasks."""
+
+    task_registry = {}
+    _name = None
+    sample_class = None
+
+    @classmethod
+    @abstractmethod
+    def create_sample(cls, *args, **kwargs) -> samples.Sample:
+        """Run the task."""
+        pass
+
+    @classmethod
+    def load_model(cls, model_path: str, model_hub: str, *args, **kwargs):
+        """Load the model."""
+
+        models = ModelAPI.model_registry
+
+        base_hubs = list(models.keys())
+        base_hubs.remove("llm")
+        supported_hubs = base_hubs + list(LANGCHAIN_HUBS.keys())
+
+        if model_hub not in supported_hubs:
+            raise AssertionError(Errors.E042.format(supported_hubs=supported_hubs))
+
+        if model_hub in LANGCHAIN_HUBS:
+            # LLM models
+            cls.model = models["llm"][cls._name].load_model(
+                hub=model_hub, path=model_path, *args, **kwargs
+            )
+        else:
+            # JSL, Huggingface, and Spacy models
+            cls.model = models[model_hub][cls._name].load_model(
+                path=model_path, *args, **kwargs
+            )
+        return cls.model
+
+    @classmethod
+    def __init_subclass__(cls, **kwargs):
+        super().__init_subclass__(**kwargs)
+        task_name = re.sub(
+            r"(?<=[a-z])(?=[A-Z][a-z])", "-", cls.__name__.replace("Task", "")
+        ).lower()
+
+        cls.task_registry[task_name] = cls
+
+    def __eq__(self, __value: object) -> bool:
+        """Check if the task is equal to another task."""
+        if isinstance(__value, str):
+            return self.__class__.__name__.replace("Task", "").lower() == __value.lower()
+        return super().__eq__(__value)
+
+    def column_mapping(self, item_keys, columns_names, *args, **kwargs):
+        """Return the column mapping."""
+
+        column_mapper = {}
+        for item in item_keys:
+            if columns_names and item in columns_names:
+                column_mapper[item] = item
+                continue
+            for key in self._default_col:
+                if item.lower() in self._default_col[key]:
+                    column_mapper[key] = item
+                    break
+        # column_mapper  values set should be in item_keys
+        if not all(
+            [
+                value.lower() in map(lambda x: x.lower(), item_keys)
+                and key in columns_names
+                for key, value in column_mapper.items()
+            ]
+        ):
+            raise ColumnNameError(list(self._default_col), item_keys)
+        return column_mapper
+
+    @property
+    def get_sample_cls(self):
+        """Return the sample class."""
+        if self.sample_class:
+            return self.sample_class
+        return None
+
+
+class TaskManager:
+    """Task manager."""
+
+    def __init__(self, task_name: str):
+        if task_name not in BaseTask.task_registry:
+            raise AssertionError(
+                Errors.E043.format(l=list(BaseTask.task_registry.keys()))
+            )
+        self.__task_name = task_name
+        self.__task: BaseTask = BaseTask.task_registry[task_name]()
+
+    def create_sample(self, *args, **kwargs):
+        """Add a task to the task manager."""
+        # filter out the key with contains column name
+        if "feature_column" in kwargs:
+            column_names = kwargs["feature_column"]
+            if isinstance(column_names, dict):
+                kwargs.pop("feature_column")
+                kwargs.update(column_names)
+
+        return self.__task.create_sample(*args, **kwargs)
+
+    def model(self, *args, **kwargs) -> "ModelAPI":
+        """Add a task to the task manager."""
+        return self.__task.load_model(*args, **kwargs)
+
+    def __eq__(self, __value: str) -> bool:
+        """Check if the task is equal to another task."""
+        return self.__task_name == __value.lower()
+
+    def __hash__(self) -> int:
+        """Return the hash of the task name."""
+        return hash(self.__task_name)
+
+    def __str__(self) -> str:
+        """Return the task name."""
+        return self.__task_name
+
+    @property
+    def task_name(self):
+        """Return the task name."""
+        return self.__task_name
+
+    @property
+    def get_sample_class(self):
+        """
+        Return the sample class.
+
+        Returns:
+            Sample: Sample class
+        """
+        return self.__task.get_sample_cls
+
+
+class NER(BaseTask):
+    """Named Entity Recognition task."""
+
+    _name = "ner"
+    _default_col = {
+        "text": ["text", "sentences", "sentence", "sample", "tokens"],
+        "ner": [
+            "label",
+            "labels ",
+            "class",
+            "classes",
+            "ner_tag",
+            "ner_tags",
+            "ner",
+            "entity",
+        ],
+        "pos": ["pos_tags", "pos_tag", "pos", "part_of_speech"],
+        "chunk": ["chunk_tags", "chunk_tag"],
+    }
+    sample_class = samples.NERSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="text",
+        target_column: str = "ner",
+        pos_tag: str = "pos",
+        chunk_tag: str = "chunk_tag",
+        *args,
+        **kwargs,
+    ) -> samples.NERSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(
+            keys, [feature_column, target_column, pos_tag, chunk_tag]
+        )
+
+        for key, value in row_data.items():
+            if isinstance(value, str):
+                row_data[key] = eval(value)
+            else:
+                row_data[key] = value
+
+        original = " ".join(row_data[column_mapper[feature_column]])
+        ner_labels = list()
+        cursor = 0
+        for token_indx in range(len(row_data[column_mapper[feature_column]])):
+            token = row_data[column_mapper[feature_column]][token_indx]
+            ner_labels.append(
+                NERPrediction.from_span(
+                    entity=row_data[column_mapper[target_column]][token_indx],
+                    word=token,
+                    start=cursor,
+                    end=cursor + len(token),
+                    pos_tag=row_data[column_mapper[pos_tag]][token_indx]
+                    if pos_tag in column_mapper and column_mapper[pos_tag] in row_data
+                    else None,
+                    chunk_tag=row_data[column_mapper[chunk_tag]][token_indx]
+                    if chunk_tag in column_mapper and column_mapper[chunk_tag] in row_data
+                    else None,
+                )
+            )
+            cursor += len(token) + 1  # +1 to account for the white space
+
+        expected_results = samples.NEROutput(predictions=ner_labels)
+
+        return samples.NERSample(original=original, expected_results=expected_results)
+
+
+class TextClassification(BaseTask):
+    """Text Classification task."""
+
+    _name = "textclassification"
+    _default_col = {
+        "text": ["text", "sentences", "sentence", "sample"],
+        "label": ["label", "labels ", "class", "classes"],
+    }
+    sample_class = samples.SequenceClassificationSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="text",
+        target_column: Union[samples.SequenceLabel, str] = "label",
+    ) -> samples.SequenceClassificationSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column, target_column])
+
+        labels = row_data.get(column_mapper[target_column])
+
+        if isinstance(labels, samples.SequenceLabel):
+            labels = [labels]
+        elif isinstance(labels, list):
+            labels = [
+                samples.SequenceLabel(label=label, score=1.0)
+                if isinstance(label, str)
+                else label
+                for label in labels
+            ]
+        else:
+            labels = [samples.SequenceLabel(label=labels, score=1.0)]
+
+        return samples.SequenceClassificationSample(
+            original=row_data[column_mapper[feature_column]],
+            expected_results=samples.SequenceClassificationOutput(predictions=labels),
+        )
+
+
+class QuestionAnswering(BaseTask):
+    """Question Answering task."""
+
+    _name = "qa"
+    _default_col = {
+        "text": ["question"],
+        "context": ["context", "passage"],
+        "answer": ["answer", "answer_and_def_correct_predictions"],
+    }
+    sample_class = samples.QASample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        dataset_name: str = "qa",
+        question: str = "text",
+        context: str = "context",
+        target_column: str = "answer",
+    ) -> samples.QASample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [question, context, target_column])
+
+        expected_results = (
+            row_data.get(column_mapper[target_column], None)
+            if target_column in column_mapper
+            else None
+        )
+        if isinstance(expected_results, str) or isinstance(expected_results, bool):
+            expected_results = [str(expected_results)]
+
+        return samples.QASample(
+            original_question=row_data[column_mapper[question]],
+            original_context=row_data.get(column_mapper.get(context, "-"), "-"),
+            expected_results=expected_results,
+            dataset_name=dataset_name,
+        )
+
+
+class Summarization(BaseTask):
+    """Summarization task."""
+
+    _name = "summarization"
+    _default_col = {"text": ["text", "document"], "summary": ["summary"]}
+    sample_class = samples.SummarizationSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="document",
+        target_column="summary",
+        dataset_name: str = "default_summarization_prompt",
+    ) -> samples.SummarizationSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column, target_column])
+
+        expected_results = row_data.get(column_mapper[target_column])
+        if isinstance(expected_results, str) or isinstance(expected_results, bool):
+            expected_results = [str(expected_results)]
+
+        return samples.SummarizationSample(
+            original=row_data[column_mapper[feature_column]],
+            expected_results=expected_results,
+            dataset_name=dataset_name,
+        )
+
+
+class Translation(BaseTask):
+    """Translation task."""
+
+    _name = "translation"
+    _default_col = {"text": ["text", "original", "sourcestring"]}
+    sample_class = samples.TranslationSample
+
+    def create_sample(
+        cls, row_data: dict, feature_column="text", dataset_name: str = "translation"
+    ) -> samples.TranslationSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column])
+
+        return samples.TranslationSample(
+            original=row_data[column_mapper[feature_column]],
+            dataset_name=dataset_name,
+        )
+
+
+class Toxicity(BaseTask):
+    """Toxicity task."""
+
+    _name = "toxicity"
+    _default_col = {"text": ["text"]}
+    sample_class = samples.ToxicitySample
+
+    def create_sample(
+        cls, row_data: dict, feature_column: str = "text", dataset_name: str = "toxicity"
+    ) -> samples.ToxicitySample:
+        """Create a sample."""
+
+        keys = list(row_data.keys())
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column])
+
+        return samples.ToxicitySample(
+            prompt=row_data[column_mapper[feature_column]],
+            dataset_name=dataset_name,
+        )
+
+
+class Security(BaseTask):
+    """Security task."""
+
+    _name = "security"
+    _default_col = {"text": ["text", "prompt"]}
+    sample_class = samples.SecuritySample
+
+    def create_sample(
+        cls, row_data: dict, feature_column="text", dataset_name: str = "security"
+    ) -> samples.SecuritySample:
+        """Create a sample."""
+
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column])
+
+        return samples.SecuritySample(
+            prompt=row_data[column_mapper[feature_column]],
+            dataset_name=dataset_name,
+        )
+
+
+class ClinicalTests(BaseTask):
+    """Clinical Tests task."""
+
+    _name = "clinicaltests"
+    _default_col = {
+        "Patient info A": [
+            "Patient info A",
+            "patient info a",
+        ],
+        "Patient info B": [
+            "Patient info B",
+            "patient info b",
+        ],
+        "Diagnosis": [
+            "Diagnosis",
+            "diagnosis",
+        ],
+    }
+    sample_class = samples.ClinicalSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        patient_info_A: str = "Patient info A",
+        patient_info_B: str = "Patient info B",
+        diagnosis: str = "Diagnosis",
+        dataset_name: str = "clinicaltests",
+    ) -> samples.ClinicalSample:
+        """Create a sample."""
+
+        keys = list(row_data.keys())
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(
+            keys, [patient_info_A, patient_info_B, diagnosis]
+        )
+
+        return samples.ClinicalSample(
+            patient_info_A=row_data[column_mapper[patient_info_A]],
+            patient_info_B=row_data[column_mapper[patient_info_B]],
+            diagnosis=row_data[column_mapper[diagnosis]],
+            dataset_name=dataset_name,
+        )
+
+
+class DisinformationTest(BaseTask):
+    """Disinformation Test task."""
+
+    _name = "disinformationtest"
+
+    _default_col = {
+        "hypothesis": ["hypothesis", "thesis"],
+        "statements": ["statements", "headlines"],
+    }
+    sample_class = samples.DisinformationSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        hypothesis: str = "hypothesis",
+        statements: str = "statements",
+        dataset_name: str = "disinformationtest",
+    ) -> samples.DisinformationSample:
+        """Create a sample."""
+
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [hypothesis, statements])
+
+        return samples.DisinformationSample(
+            hypothesis=row_data[column_mapper[hypothesis]],
+            statements=row_data[column_mapper[statements]],
+            dataset_name=dataset_name,
+        )
+
+
+class Political(BaseTask):
+    """Political task."""
+
+    _name = "political"
+    _default_col = {"text": ["text", "question"]}
+    sample_class = samples.LLMAnswerSample
+
+    def create_sample(
+        cls, row_data: dict, feature_column="question", dataset_name: str = "political"
+    ) -> samples.LLMAnswerSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column])
+
+        return samples.LLMAnswerSample(
+            question=row_data[column_mapper[feature_column]],
+            dataset_name=dataset_name,
+        )
+
+
+class WinoBias(BaseTask):
+    """WinoBias task."""
+
+    _name = "winobias"
+    _default_col = {
+        "text": ["text", "prompt"],
+        "options": ["options", "choices"],
+    }
+    sample_class = samples.WinoBiasSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="text",
+        options: str = "options",
+        dataset_name: str = "winobias",
+    ) -> samples.WinoBiasSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column, options])
+
+        return samples.WinoBiasSample(
+            masked_text=row_data[column_mapper[feature_column]],
+            options=row_data[column_mapper[options]],
+            dataset_name=dataset_name,
+        )
+
+
+class LegalTests(BaseTask):
+    """Legal task."""
+
+    _name = "legal"
+    _default_col = (
+        {
+            "case": ["case"],
+            "legal-claim": ["legal-claim"],
+            "legal_conclusion_a": ["legal_conclusion_a"],
+            "legal_conclusion_b": ["legal_conclusion_b"],
+            "correct_choice": ["correct_choice"],
+        },
+    )
+    sample_class = samples.LegalSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="case",
+        legal_claim: str = "legal-claim",
+        legal_conclusion_A: str = "legal_conclusion_a",
+        legal_conclusion_B: str = "legal_conclusion_b",
+        correct_choice: str = "correct_choice",
+        dataset_name: str = "legal",
+    ) -> samples.LegalSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(
+            keys,
+            [
+                feature_column,
+                legal_claim,
+                legal_conclusion_A,
+                legal_conclusion_B,
+                correct_choice,
+            ],
+        )
+
+        return samples.LegalSample(
+            case=row_data[column_mapper[feature_column]],
+            legal_claim=row_data[column_mapper[legal_claim]],
+            legal_conclusion_A=row_data[column_mapper[legal_conclusion_A]],
+            legal_conclusion_B=row_data[column_mapper[legal_conclusion_B]],
+            correct_conlusion=row_data[column_mapper[correct_choice]],
+            dataset_name=dataset_name,
+        )
+
+
+class FactualityTest(BaseTask):
+    """Factuality task."""
+
+    _name = "factuality"
+    _default_col = {
+        "article_sent": ["article_sent"],
+        "correct_sent": ["correct_sent"],
+        "incorrect_sent": ["incorrect_sent"],
+    }
+    sample_class = samples.FactualitySample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="article_sent",
+        correct_sent: str = "correct_sent",
+        incorrect_sent: str = "incorrect_sent",
+        dataset_name: str = "factuality",
+    ) -> samples.FactualitySample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(
+            keys, [feature_column, correct_sent, incorrect_sent]
+        )
+
+        return samples.FactualitySample(
+            article_sent=row_data[column_mapper[feature_column]],
+            correct_sent=row_data[column_mapper[correct_sent]],
+            incorrect_sent=row_data[column_mapper[incorrect_sent]],
+            dataset_name=dataset_name,
+        )
+
+
+class SensitivityTest(BaseTask):
+    """Sensitivity task."""
+
+    _name = "sensitivity"
+    _default_col = {"text": ["text", "question"]}
+    sample_class = samples.SensitivitySample
+
+    def create_sample(
+        cls, row_data: dict, feature_column="text", *args, **kwargs
+    ) -> samples.SensitivitySample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column])
+
+        return samples.SensitivitySample(
+            original=row_data[column_mapper[feature_column]],
+            *args,
+            **kwargs,
+        )
+
+
+class CrowsPairs(BaseTask):
+    """Crows Pairs task."""
+
+    _name = "crowspairs"
+    _default_col = {
+        "text": ["text", "sentence"],
+        "mask1": ["mask1"],
+        "mask2": ["mask2"],
+    }
+    sample_class = samples.CrowsPairsSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="sentence",
+        mask1: str = "mask1",
+        mask2: str = "mask2",
+        *args,
+        **kwargs,
+    ) -> samples.CrowsPairsSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column, mask1, mask2])
+
+        return samples.CrowsPairsSample(
+            sentence=row_data[column_mapper[feature_column]],
+            mask1=row_data[column_mapper[mask1]],
+            mask2=row_data[column_mapper[mask2]],
+        )
+
+
+class Stereoset(BaseTask):
+    """StereoSet task."""
+
+    _name = "stereoset"
+    _default_col = {
+        "text": ["text", "sentence"],
+        "mask1": ["mask1"],
+        "mask2": ["mask2"],
+    }
+    sample_class = samples.StereoSetSample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        bias_type: str = "bias_type",
+        test_type: str = "type",
+        target_column: str = "target",
+        context: str = "context",
+        sent_stereo: str = "stereotype",
+        sent_antistereo: str = "anti-stereotype",
+        sent_unrelated: str = "unrelated",
+        *args,
+        **kwargs,
+    ) -> samples.StereoSetSample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(
+            keys,
+            [
+                bias_type,
+                test_type,
+                target_column,
+                context,
+                sent_stereo,
+                sent_antistereo,
+                sent_unrelated,
+            ],
+        )
+
+        return samples.StereoSetSample(
+            test_type=row_data[column_mapper[test_type]],
+            bias_type=row_data[column_mapper[bias_type]],
+            target=row_data[column_mapper[target_column]],
+            context=row_data[column_mapper[context]],
+            sent_stereo=row_data[column_mapper[sent_stereo]],
+            sent_antistereo=row_data[column_mapper[sent_antistereo]],
+            sent_unrelated=row_data[column_mapper[sent_unrelated]],
+        )
+
+
+class SycophancyTest(BaseTask):
+    """Sycophancy task."""
+
+    _name = "sycophancytest"
+    _default_col = {
+        "text": ["text", "question"],
+        "answer": ["answer", "answer_and_def_correct_predictions"],
+    }
+    sample_class = samples.SycophancySample
+
+    def create_sample(
+        cls,
+        row_data: dict,
+        feature_column="question",
+        target_column="answer",
+        dataset_name: str = "",
+        *args,
+        **kwargs,
+    ) -> samples.SycophancySample:
+        """Create a sample."""
+        keys = list(row_data.keys())
+
+        # auto-detect the default column names from the row_data
+        column_mapper = cls.column_mapping(keys, [feature_column, target_column])
+
+        return samples.SycophancySample(
+            original_question=row_data[column_mapper[feature_column]],
+            ground_truth=row_data[column_mapper[target_column]],
+            dataset_name=dataset_name,
+            *args,
+            **kwargs,
+        )
diff --git a/langtest/testrunner.py b/langtest/testrunner.py
deleted file mode 100644
index 3dbf9b799..000000000
--- a/langtest/testrunner.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import pandas as pd
-from tqdm import tqdm
-from langtest.modelhandler import ModelFactory
-from typing import List, Tuple
-from langtest.utils.custom_types import Sample
-
-
-class BaseRunner:
-    """Base class for running tests on models."""
-
-    def __init__(self, load_testcases: List[Sample], model_handler: ModelFactory) -> None:
-        """Initialize the BaseRunner class.
-
-        Args:
-            load_testcases (List[Sample]): List containing the testcases to be evaluated.
-            model_handler (ModelFactory): Object representing the model handler, either spaCy, SparkNLP or transformer.
-        """
-        self.load_testcases = load_testcases.copy()
-        self._model_handler = model_handler
-
-    # @abc.abstractmethod
-    def evaluate(self) -> Tuple[List[Sample], pd.DataFrame]:
-        """Abstract method to evaluate the testcases.
-
-        Returns:
-            Tuple[List[Sample], pd.DataFrame]
-        """
-        test_result = TestRunner(self.load_testcases, self._model_handler).evaluate()
-        return test_result
-
-
-class TestRunner(BaseRunner):
-    """Class for running robustness tests on models."""
-
-    def evaluate(self):
-        """Evaluate the testcases and return the evaluation results.
-
-        Returns:
-            List[Sample]:
-                all containing the predictions for both the original text and the perturbed one
-        """
-        for sample in tqdm(self.load_testcases, desc="Running test cases..."):
-            if sample.state != "done":
-                if sample.category not in ["Robustness", "Bias"]:
-                    sample.expected_results = self._model_handler(sample.original)
-                sample.actual_results = self._model_handler(sample.test_case)
-                sample.state = "done"
-
-        return self.load_testcases
diff --git a/langtest/transform/__init__.py b/langtest/transform/__init__.py
index 6c0cdb5ee..d06788ce3 100644
--- a/langtest/transform/__init__.py
+++ b/langtest/transform/__init__.py
@@ -38,7 +38,7 @@
     white_names,
 )
 from .utils import get_substitution_names, create_terminology, filter_unique_samples
-from ..modelhandler import ModelFactory
+from ..modelhandler import ModelAPI
 from ..utils.custom_types.sample import (
     NERSample,
     QASample,
@@ -48,6 +48,7 @@
 )
 from ..utils.custom_types.helpers import default_user_prompt
 from ..utils.util_metrics import calculate_f1_score
+from ..errors import Errors, Warnings
 
 nest_asyncio.apply()
 
@@ -89,7 +90,7 @@ def transform(
                 The data to be tested.
             test_types : dict
                 A dictionary mapping test category names to lists of test scenario names.
-            model: ModelFactory
+            model: ModelAPI
                 Model to be tested.
 
         Returns:
@@ -120,11 +121,16 @@ def transform(
                     )
                     if task not in supported:
                         raise ValueError(
-                            f'The test type "{sub_test}" is not supported for the task "{task}". "{sub_test}" only supports {supported}.'
+                            Errors.E046.format(
+                                sub_test=sub_test, task=task, supported=supported
+                            )
                         )
             elif test_category != "defaults":
                 raise ValueError(
-                    f"The test category {test_category} does not exist. Available categories are: {all_categories.keys()}."
+                    Errors.E047.format(
+                        test_category=test_category,
+                        available_categories=list(all_categories.keys()),
+                    )
                 )
 
         # Generate testcases
@@ -169,13 +175,13 @@ def test_scenarios(cls):
         }
 
     @staticmethod
-    def run(samples_list: List[Sample], model_handler: ModelFactory, **kwargs):
+    def run(samples_list: List[Sample], model_handler: ModelAPI, **kwargs):
         """
         Runs the specified tests on the given data and returns a list of results.
 
         Args:
             samples_list : List[Sample]
-            model_handler : ModelFactory
+            model_handler : ModelAPI
 
         """
         async_tests = TestFactory.async_run(samples_list, model_handler, **kwargs)
@@ -195,14 +201,14 @@ def run(samples_list: List[Sample], model_handler: ModelFactory, **kwargs):
 
     @classmethod
     async def async_run(
-        cls, samples_list: List[Sample], model_handler: ModelFactory, **kwargs
+        cls, samples_list: List[Sample], model_handler: ModelAPI, **kwargs
     ):
         """
         Runs the specified tests on the given data and returns a list of results.
 
         Args:
             samples_list : List[Sample]
-            model_handler : ModelFactory
+            model_handler : ModelAPI
 
         """
         hash_samples = {}
@@ -268,7 +274,7 @@ def available_tests():
 
     @classmethod
     def run(
-        cls, sample_list: Dict[str, List[Sample]], model: ModelFactory, **kwargs
+        cls, sample_list: Dict[str, List[Sample]], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """
         Runs the specified tests on the given data and returns a list of results.
@@ -276,8 +282,8 @@ def run(
         Args:
             sample_list (Dict[str, List[Sample]]):
                 A dictionary mapping test scenario names to a list of `Sample` objects.
-            model (ModelFactory):
-                A `ModelFactory` object representing the model to be tested.
+            model (ModelAPI):
+                A `ModelAPI` object representing the model to be tested.
 
         Returns:
             List[Sample]: A list of `Sample` objects with the test results.
@@ -320,10 +326,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -331,7 +334,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
         if "swap_entities" in self.tests:
@@ -446,9 +452,7 @@ def transform(self) -> List[Sample]:
 
                         transformed_samples.extend(transformed_samples_perturbation)
                     elif key != "min_pass_rate":
-                        raise ValueError(
-                            f"Invalid perturbation {key} in multiple_perturbations.Please use perturbations1, perturbations2, ..."
-                        )
+                        raise ValueError(Errors.E050.format(key=key))
 
             elif (
                 test_name == "multiple_perturbations"
@@ -489,14 +493,10 @@ def transform(self) -> List[Sample]:
                         transformed_samples.extend(transformed_samples_perturbation)
 
                     elif key not in ("min_pass_rate", "prob"):
-                        raise ValueError(
-                            f"Invalid perturbation {key} in multiple_perturbations. Please use perturbations1, perturbations2, perturbations3 ..."
-                        )
+                        raise ValueError(Errors.E050.format(key=key))
 
             elif test_name == "multiple_perturbations" and TestFactory.task == "ner":
-                raise ValueError(
-                    "multiple_perturbations test is not supported for NER task"
-                )
+                raise ValueError(Errors.E051)
 
             else:
                 transformed_samples = test_func(
@@ -512,11 +512,11 @@ def transform(self) -> List[Sample]:
             no_transformation_applied_tests.update(removed_samples_tests)
 
         if no_transformation_applied_tests:
-            warning_message = (
-                "Removing samples where no transformation has been applied:\n"
-            )
+            warning_message = Warnings.W009
             for test, count in no_transformation_applied_tests.items():
-                warning_message += f"- Test '{test}': {count} samples removed out of {len(self._data_handler)}\n"
+                warning_message += Warnings.W010.format(
+                    test=test, count=count, total_sample=len(self._data_handler)
+                )
 
             logging.warning(warning_message)
 
@@ -552,10 +552,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -563,7 +560,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
         if "replace_to_male_pronouns" in self.tests:
@@ -711,11 +711,11 @@ def transform(self) -> List[Sample]:
             no_transformation_applied_tests.update(removed_samples_tests)
 
         if no_transformation_applied_tests:
-            warning_message = (
-                "Removing samples where no transformation has been applied:\n"
-            )
+            warning_message = Warnings.W009
             for test, count in no_transformation_applied_tests.items():
-                warning_message += f"- Test '{test}': {count} samples removed out of {len(self._data_handler)}\n"
+                warning_message += Warnings.W010.format(
+                    test=test, count=count, total_sample=len(self._data_handler)
+                )
 
             logging.warning(warning_message)
 
@@ -753,10 +753,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -764,7 +761,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
     def transform(self) -> List[Sample]:
@@ -820,10 +820,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict, **kwargs) -> None:
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -831,7 +828,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict, **kwargs) -> None:
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
     def transform(self) -> List[Sample]:
@@ -845,9 +845,7 @@ def transform(self) -> List[Sample]:
         all_samples = []
 
         if self._data_handler[0].expected_results is None:
-            raise RuntimeError(
-                "This dataset does not contain labels and fairness tests cannot be run with it."
-            )
+            raise RuntimeError(Errors.E052.format(var="fairness"))
 
         for test_name, params in self.tests.items():
             transformed_samples = self.supported_tests[test_name].transform(
@@ -879,7 +877,7 @@ def available_tests() -> dict:
     def run(
         cls,
         sample_list: Dict[str, List[Sample]],
-        model: ModelFactory,
+        model: ModelAPI,
         raw_data: List[Sample],
         **kwargs,
     ):
@@ -888,7 +886,7 @@ def run(
 
         Args:
             sample_list (Dict[str, List[Sample]]): A dictionary of test names and corresponding `Sample` objects.
-            model (ModelFactory): The model to be tested.
+            model (ModelAPI): The model to be tested.
             raw_data (List[Sample]): The raw dataset.
 
         """
@@ -936,9 +934,7 @@ def run(
                     )
 
                     if data[0].expected_results is None:
-                        raise RuntimeError(
-                            f"The dataset {dataset_name} does not contain labels and fairness tests cannot be run with it. Skipping the fairness tests."
-                        )
+                        raise RuntimeError(Errors.E053.format(dataset_name=dataset_name))
                     y_true = pd.Series(data).apply(lambda x: x.expected_results)
                     X_test = pd.Series(data)
                     y_pred = X_test.apply(
@@ -964,9 +960,7 @@ def run(
                         "user_prompt", default_user_prompt.get(dataset_name, "")
                     )
                     if data[0].expected_results is None:
-                        raise RuntimeError(
-                            f"The dataset {dataset_name} does not contain labels and fairness tests cannot be run with it. Skipping the fairness tests."
-                        )
+                        raise RuntimeError(Errors.E053.format(dataset_name=dataset_name))
 
                     y_true = pd.Series(data).apply(lambda x: x.expected_results)
                     X_test = pd.Series(data)
@@ -1038,10 +1032,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict, **kwargs) -> None:
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -1049,7 +1040,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict, **kwargs) -> None:
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
     def transform(self) -> List[Sample]:
@@ -1063,9 +1057,7 @@ def transform(self) -> List[Sample]:
         all_samples = []
 
         if self._data_handler[0].expected_results is None:
-            raise RuntimeError(
-                "This dataset does not contain labels and accuracy tests cannot be run with it."
-            )
+            raise RuntimeError(Errors.E052.format(var="accuracy"))
 
         for test_name, params in self.tests.items():
             data_handler_copy = [x.copy() for x in self._data_handler]
@@ -1123,7 +1115,7 @@ def available_tests() -> dict:
     def run(
         cls,
         sample_list: Dict[str, List[Sample]],
-        model: ModelFactory,
+        model: ModelAPI,
         raw_data: List[Sample],
         **kwargs,
     ):
@@ -1132,7 +1124,7 @@ def run(
 
         Args:
             sample_list (Dict[str, List[Sample]]): A dictionary of test names and corresponding `Sample` objects.
-            model (ModelFactory): The model to be tested.
+            model (ModelAPI): The model to be tested.
             raw_data (List[Sample]): The raw dataset.
 
         """
@@ -1237,10 +1229,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict, **kwargs) -> None:
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -1248,7 +1237,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict, **kwargs) -> None:
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
     def transform(self) -> List[Sample]:
@@ -1330,13 +1322,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs the model performance
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the model performance
 
         Returns:
@@ -1393,7 +1385,7 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         supported_tests = cls.available_tests()
         tasks = []
@@ -1445,13 +1437,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs the clinical tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the clinical tests
 
         Returns:
@@ -1470,12 +1462,12 @@ def available_tests(cls) -> Dict[str, str]:
         """
         return {"demographic-bias": cls}
 
-    async def async_run(sample_list: List[Sample], model: ModelFactory, *args, **kwargs):
+    async def async_run(sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         """Runs the clinical tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the clinical tests
 
         Returns:
@@ -1516,7 +1508,7 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         task = asyncio.create_task(cls.async_run(sample_list, model, **kwargs))
         return task
@@ -1525,7 +1517,7 @@ async def run(
     def available_tests(cls) -> Dict[str, str]:
         return {"narrative_wedging": cls}
 
-    async def async_run(sample_list: List[Sample], model: ModelFactory, *args, **kwargs):
+    async def async_run(sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         progress = kwargs.get("progress_bar", False)
         for sample in sample_list["narrative_wedging"]:
             if sample.state != "done":
@@ -1563,13 +1555,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs the model performance
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the model performance
 
         Returns:
@@ -1636,10 +1628,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -1647,7 +1636,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
     def transform(self) -> List[Sample]:
@@ -1688,11 +1680,11 @@ def transform(self) -> List[Sample]:
             no_transformation_applied_tests.update(removed_samples_tests)
 
         if no_transformation_applied_tests:
-            warning_message = (
-                "Removing samples where no transformation has been applied:\n"
-            )
+            warning_message = Warnings.W009
             for test, count in no_transformation_applied_tests.items():
-                warning_message += f"- Test '{test}': {count} samples removed out of {len(self._data_handler)}\n"
+                warning_message += Warnings.W010.format(
+                    test=test, count=count, total_sample=len(self._data_handler)
+                )
 
             logging.warning(warning_message)
 
@@ -1743,13 +1735,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs the wino-bias tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the wino-bias tests
 
         Returns:
@@ -1768,12 +1760,12 @@ def available_tests(cls) -> Dict[str, str]:
         """
         return {"gender-occupational-stereotype": cls}
 
-    async def async_run(sample_list: List[Sample], model: ModelFactory, *args, **kwargs):
+    async def async_run(sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         """Runs the clinical tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the wino-bias tests
 
         Returns:
@@ -1821,13 +1813,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs the crows-pairs tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the crows-pairs tests
 
         Returns:
@@ -1847,12 +1839,12 @@ def available_tests(cls) -> Dict[str, str]:
         return {"common-stereotypes": cls}
 
     @staticmethod
-    async def async_run(sample_list: List[Sample], model: ModelFactory, *args, **kwargs):
+    async def async_run(sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         """Runs the clinical tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the crows-pairs tests
 
         Returns:
@@ -1908,13 +1900,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs the stereoset tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the stereoset tests
 
         Returns:
@@ -1934,12 +1926,12 @@ def available_tests(cls) -> Dict[str, str]:
         return {"intrasentence": cls, "intersentence": cls}
 
     @staticmethod
-    async def async_run(sample_list: List[Sample], model: ModelFactory, *args, **kwargs):
+    async def async_run(sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         """Runs the clinical tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the crows-pairs tests
 
         Returns:
@@ -1997,13 +1989,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs the legal tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the wino-bias tests
 
         Returns:
@@ -2022,12 +2014,12 @@ def available_tests(cls) -> Dict[str, str]:
         """
         return {"legal-support": cls}
 
-    async def async_run(sample_list: List[Sample], model: ModelFactory, *args, **kwargs):
+    async def async_run(sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         """Runs the legal tests
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the legal tests
 
         Returns:
@@ -2075,13 +2067,13 @@ def transform(self) -> List[Sample]:
 
     @classmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Runs factuality tests
 
         Args:
             sample_list (list[Sample]): A list of Sample objects to be tested.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional keyword arguments.
 
         Returns:
@@ -2101,12 +2093,12 @@ def available_tests(cls) -> Dict[str, str]:
         """
         return {"order_bias": cls}
 
-    async def async_run(sample_list: List[Sample], model: ModelFactory, *args, **kwargs):
+    async def async_run(sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         """Runs factuality tests
 
         Args:
             sample_list (list[Sample]): A list of Sample objects to be tested.
-            model (ModelFactory): The model to be used for testing.
+            model (ModelAPI): The model to be used for testing.
             *args: Additional positional arguments.
             **kwargs: Additional keyword arguments.
 
@@ -2157,10 +2149,7 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         self.kwargs = kwargs
 
         if not isinstance(self.tests, dict):
-            raise ValueError(
-                "Invalid test configuration! Tests can be "
-                "[1] dictionary of test name and corresponding parameters."
-            )
+            raise ValueError(Errors.E048)
 
         if len(self.tests) == 0:
             self.tests = self.supported_tests
@@ -2168,7 +2157,10 @@ def __init__(self, data_handler: List[Sample], tests: Dict = None, **kwargs) ->
         not_supported_tests = set(self.tests) - set(self.supported_tests)
         if len(not_supported_tests) > 0:
             raise ValueError(
-                f"Invalid test specification: {not_supported_tests}. Available tests are: {list(self.supported_tests.keys())}"
+                Errors.E049.format(
+                    not_supported_tests=not_supported_tests,
+                    supported_tests=list(self.supported_tests.keys()),
+                )
             )
 
     def transform(self) -> List[Sample]:
diff --git a/langtest/transform/bias.py b/langtest/transform/bias.py
index 7bf154aad..e546a3e85 100644
--- a/langtest/transform/bias.py
+++ b/langtest/transform/bias.py
@@ -4,7 +4,7 @@
 from abc import ABC, abstractmethod
 from typing import List
 
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
 from .constants import female_pronouns, male_pronouns, neutral_pronouns
 from ..utils.custom_types import Sample, Span, Transformation
 
@@ -41,14 +41,12 @@ def transform(self, sample_list: List[Sample], *args, **kwargs) -> List[Sample]:
 
     @staticmethod
     @abstractmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Abstract method that implements the bias measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the bias measure.
+            model (ModelAPI): The model to be used for the bias measure.
 
         Returns:
             List[Sample]: The transformed data based on the implemented bias measure.
@@ -70,12 +68,12 @@ async def run(
         return sample_list
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Abstract method that implements the creation of an asyncio task for the bias measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the bias measure.
+            model (ModelAPI): The model to be used for the bias measure.
 
         Returns:
             asyncio.Task: The asyncio task for the bias measure.
diff --git a/langtest/transform/custom_data.py b/langtest/transform/custom_data.py
index fe721f1c1..acbd0804f 100644
--- a/langtest/transform/custom_data.py
+++ b/langtest/transform/custom_data.py
@@ -12,6 +12,7 @@
     female_pronouns,
 )
 from typing import Union
+from ..errors import Errors
 
 
 def add_custom_data(data: Union[list, dict], name: str, append: bool) -> None:
@@ -38,9 +39,7 @@ def add_custom_data(data: Union[list, dict], name: str, append: bool) -> None:
 
         # Validate the schema
         if not set(data.keys()).issubset(valid_names):
-            raise ValueError(
-                f"Invalid schema. It should be one of: {', '.join(valid_names)}."
-            )
+            raise ValueError(Errors.E054.format(var=", ".join(valid_names)))
 
         if append:
             # Append unique values to existing keys
@@ -59,9 +58,7 @@ def add_custom_data(data: Union[list, dict], name: str, append: bool) -> None:
 
         # Validate the schema
         if not set(data.keys()).issubset(valid_names):
-            raise ValueError(
-                f"Invalid schema. It should be one of: {', '.join(valid_names)}."
-            )
+            raise ValueError(Errors.E054.format(var=", ".join(valid_names)))
 
         if append:
             # Append unique values to existing keys
@@ -89,33 +86,28 @@ def add_custom_data(data: Union[list, dict], name: str, append: bool) -> None:
 
         for data_dict in data:
             if "name" not in data_dict:
-                raise ValueError("Invalid JSON format. 'name' key is missing.")
+                raise ValueError(Errors.E055)
 
             name = data_dict["name"]
             first_names = data_dict.get("first_names", [])
             last_names = data_dict.get("last_names", [])
 
             if not isinstance(name, str):
-                raise ValueError("Invalid 'name' format in the JSON file.")
+                raise ValueError(Errors.E057)
 
             if name not in valid_names:
                 raise ValueError(
-                    f"Invalid 'name' value '{name}'. It should be one of: {', '.join(valid_names)}."
+                    Errors.E056.format(var1=name, var2=", ".join(valid_names))
                 )
 
             if not first_names and not last_names:
                 if name not in ("native_american_names", "inter_racial_names"):
-                    raise ValueError(
-                        f"At least one of 'first_names' or 'last_names' must be specified for '{name}'."
-                    )
+                    raise ValueError(Errors.E058.format(name=name))
                 else:
-                    raise ValueError(f"'last_names' must be specified for '{name}'.")
+                    raise ValueError(Errors.E059.format(name=name))
 
             if set(data_dict.keys()) - {"name", "first_names", "last_names"}:
-                raise ValueError(
-                    f"Invalid keys in the JSON for '{name}'. "
-                    f"Only the following keys are allowed: 'name', 'first_names', 'last_names'."
-                )
+                raise ValueError(Errors.E060.format(name=name))
 
             if name in (
                 "white_names",
@@ -141,13 +133,13 @@ def add_custom_data(data: Union[list, dict], name: str, append: bool) -> None:
         # Validate the schema
         for data_dict in data:
             if "name" not in data_dict:
-                raise ValueError("Invalid JSON format. 'name' key is missing.")
+                raise ValueError(Errors.E055)
 
             name = data_dict["name"]
 
             if name not in valid_names:
                 raise ValueError(
-                    f"Invalid 'name' value '{name}'. It should be one of: {', '.join(valid_names)}."
+                    Errors.E056.format(var1=name, var2=", ".join(valid_names))
                 )
 
             pronouns = {
@@ -166,10 +158,7 @@ def add_custom_data(data: Union[list, dict], name: str, append: bool) -> None:
                     "possessive_pronouns",
                 ]
             ):
-                raise ValueError(
-                    f"Missing pronoun keys in the JSON for '{name}'. Please include at least one of: "
-                    "'subjective_pronouns', 'objective_pronouns', 'reflexive_pronouns', 'possessive_pronouns'."
-                )
+                raise ValueError(Errors.E061.format(name=name))
 
             invalid_keys = set(data_dict.keys()) - {
                 "name",
@@ -180,9 +169,7 @@ def add_custom_data(data: Union[list, dict], name: str, append: bool) -> None:
             }
             if invalid_keys:
                 raise ValueError(
-                    f"Invalid keys in the JSON for '{name}': {', '.join(invalid_keys)}. "
-                    f"Only the following keys are allowed: "
-                    "'name', 'subjective_pronouns', 'objective_pronouns', 'reflexive_pronouns', 'possessive_pronouns'."
+                    Errors.E062.format(var1=name, var2=", ".join(invalid_keys))
                 )
 
             bias_dict = {
diff --git a/langtest/transform/fairness.py b/langtest/transform/fairness.py
index 13df85acc..c2b5405df 100644
--- a/langtest/transform/fairness.py
+++ b/langtest/transform/fairness.py
@@ -2,7 +2,7 @@
 from abc import ABC, abstractmethod
 from typing import List, Dict, Union
 
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
 from langtest.utils.custom_types import (
     MaxScoreOutput,
     MaxScoreSample,
@@ -51,7 +51,7 @@ async def run(
 
         Args:
             sample_list (List[MinScoreSample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
         Returns:
             List[MinScoreSample]: The transformed samples.
@@ -59,12 +59,12 @@ async def run(
         raise NotImplementedError()
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Creates a task for the run method.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
         Returns:
             asyncio.Task: The task for the run method.
@@ -135,7 +135,7 @@ async def run(
 
         Args:
             sample_list (List[MinScoreSample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
         Returns:
             List[MinScoreSample]: The transformed samples.
@@ -218,7 +218,7 @@ async def run(
 
         Args:
             sample_list (List[MaxScoreSample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
 
         Returns:
@@ -310,7 +310,7 @@ async def run(
 
         Args:
             sample_list (List[MinScoreSample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
         Returns:
             List[MinScoreSample]: The transformed samples.
@@ -410,7 +410,7 @@ async def run(
 
         Args:
             sample_list (List[MaxScoreSample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
         Returns:
             List[MaxScoreSample]: The transformed samples.
diff --git a/langtest/transform/performance.py b/langtest/transform/performance.py
index 35d2f363a..ef5bdea34 100644
--- a/langtest/transform/performance.py
+++ b/langtest/transform/performance.py
@@ -1,8 +1,9 @@
 import asyncio
 import time
 from typing import List
+from ..errors import Errors
 from abc import ABC, abstractmethod
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
 from langtest.utils.custom_types.sample import Sample, SpeedTestSample
 
 
@@ -33,18 +34,16 @@ def transform():
         Raises:
             NotImplementedError: This method must be implemented in the derived class.
         """
-        raise NotImplementedError("Please Implement this method")
+        raise NotImplementedError(Errors.E063)
 
     @staticmethod
     @abstractmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Abstract method that implements the model performance.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the model performance.
 
         Returns:
@@ -67,12 +66,12 @@ async def run(
         return sample_list
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Creates a task to run the model performance.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the model performance.
 
         Returns:
diff --git a/langtest/transform/political.py b/langtest/transform/political.py
index d509dab2c..038ae6d9e 100644
--- a/langtest/transform/political.py
+++ b/langtest/transform/political.py
@@ -1,7 +1,7 @@
 import asyncio
 from typing import List
 
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
 from .constants import political_compass_questions
 from ..utils.custom_types import LLMAnswerSample, Sample
 from abc import ABC, abstractmethod
@@ -34,14 +34,12 @@ def transform(self, sample_list: List[Sample], *args, **kwargs) -> List[Sample]:
 
     @staticmethod
     @abstractmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Abstract method that implements the political measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the political measure.
+            model (ModelAPI): The model to be used for the political measure.
 
         Returns:
             List[Sample]: The transformed data based on the implemented political measure.
@@ -50,12 +48,12 @@ async def run(
         raise NotImplementedError()
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Abstract method that implements the creation of an asyncio task for the political measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the political measure.
+            model (ModelAPI): The model to be used for the political measure.
 
         Returns:
             asyncio.Task: The asyncio task for the political measure.
@@ -95,14 +93,12 @@ def transform(sample_list: List[Sample]) -> List[Sample]:
         return samples
 
     @staticmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Abstract method that implements the political measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the political measure.
+            model (ModelAPI): The model to be used for the political measure.
 
         Returns:
             List[Sample]: The transformed data based on the implemented political measure.
diff --git a/langtest/transform/representation.py b/langtest/transform/representation.py
index 0d234298e..61bf8d0ff 100644
--- a/langtest/transform/representation.py
+++ b/langtest/transform/representation.py
@@ -1,8 +1,9 @@
 import asyncio
+from ..errors import Errors
 from abc import ABC, abstractmethod
 from typing import List, Dict, Union
 
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
 from langtest.utils.custom_types import (
     MinScoreOutput,
     MinScoreQASample,
@@ -59,13 +60,13 @@ def transform(
     @classmethod
     @abstractmethod
     async def run(
-        cls, sample_list: List[Sample], model: ModelFactory, **kwargs
+        cls, sample_list: List[Sample], model: ModelAPI, **kwargs
     ) -> List[Sample]:
         """Computes the score for the given data.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
         Returns:
             List[Sample]: The transformed samples.
@@ -73,12 +74,12 @@ async def run(
         raise NotImplementedError()
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Creates a task for the run method.
 
         Args:
             sample_list (List[Sample]): The input data to be evaluated for representation test.
-            model (ModelFactory): The model to be used for the computation.
+            model (ModelAPI): The model to be used for the computation.
 
         Returns:
             asyncio.Task: The task for the run method.
@@ -162,8 +163,7 @@ def transform(
                 min_proportions = params["min_proportion"]
                 if sum(min_proportions.values()) > 1:
                     raise ValueError(
-                        "Sum of proportions cannot be greater than 1. "
-                        "So min_gender_representation_proportion test cannot run."
+                        Errors.E064.format(var="min_gender_representation_proportion")
                     )
 
             for key, value in min_proportions.items():
@@ -192,14 +192,12 @@ def transform(
         return samples
 
     @staticmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Computes the actual results for the Gender Representation test.
 
         Args:
             sample_list (List[Sample]): The input data to be evaluated for representation test.
-            model (ModelFactory): The model factory object.
+            model (ModelAPI): The model factory object.
 
         Returns:
             List[Sample]: The list of samples with actual results.
@@ -348,11 +346,11 @@ def transform(
                     expected_representation = params["min_proportion"]
 
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            """Sum of proportions cannot be greater than 1.
-                            So min_ethnicity_name_representation_proportion test cannot run"""
+                        raise ValueError(
+                            Errors.E064.format(
+                                var="min_ethnicity_name_representation_proportion"
+                            )
                         )
-                        raise ValueError()
 
                 elif isinstance(params["min_proportion"], float):
                     expected_representation = {
@@ -360,12 +358,11 @@ def transform(
                         for key in default_ehtnicity_representation
                     }
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            """Sum of proportions cannot be greater than 1.
-                        So min_ethnicity_name_representation_proportion test cannot run"""
+                        raise ValueError(
+                            Errors.E064.format(
+                                var="min_ethnicity_name_representation_proportion"
+                            )
                         )
-                        raise ValueError()
-
             for key, value in expected_representation.items():
                 if hasattr(data[0], "task") and data[0].task == "question-answering":
                     sample = MinScoreQASample(
@@ -393,14 +390,12 @@ def transform(
         return sample_list
 
     @staticmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Computes the actual results for the ethnicity representation test.
 
         Args:
             sample_list (List[Sample]): The input data to be evaluated for representation test.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
 
         Returns:
             List[Sample]: The list of samples with actual results.
@@ -515,22 +510,18 @@ def transform(
                     expected_representation = params["min_proportion"]
 
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            """Sum of proportions cannot be greater than 1.
-                        So min_label_representation_proportion test cannot run"""
+                        raise ValueError(
+                            Errors.E064.format(var="min_label_representation_proportion")
                         )
-                        raise ValueError()
 
                 elif isinstance(params["min_proportion"], float):
                     expected_representation = {
                         key: params["min_proportion"] for key in labels
                     }
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            """Sum of proportions cannot be greater than 1.
-                        So min_label_representation_proportion test cannot run"""
+                        raise ValueError(
+                            Errors.E064.format(var="min_label_representation_proportion")
                         )
-                        raise ValueError()
 
             for key, value in expected_representation.items():
                 sample = MinScoreSample(
@@ -545,14 +536,12 @@ def transform(
         return sample_list
 
     @staticmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Computes the actual representation of the labels in the dataset.
 
         Args:
             sample_list (List[Sample]): The input data to be evaluated for representation test.
-            model (ModelFactory): The model to be evaluated.
+            model (ModelAPI): The model to be evaluated.
 
         Returns:
             List[Sample]: Label Representation test results.
@@ -691,11 +680,11 @@ def transform(
                     expected_representation = params["min_proportion"]
 
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            """Sum of proportions cannot be greater than 1.
-                        So min_religion_name_representation_proportion test cannot run"""
+                        raise ValueError(
+                            Errors.E064.format(
+                                var="min_religion_name_representation_proportion"
+                            )
                         )
-                        raise ValueError()
 
                 elif isinstance(params["min_proportion"], float):
                     expected_representation = {
@@ -703,11 +692,11 @@ def transform(
                         for key in default_religion_representation
                     }
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            """Sum of proportions cannot be greater than 1.
-                        So min_religion_name_representation_proportion test cannot run"""
+                        raise ValueError(
+                            Errors.E064.format(
+                                var="min_religion_name_representation_proportion"
+                            )
                         )
-                        raise ValueError()
 
             entity_representation = (
                 RepresentationOperation.get_religion_name_representation_dict(data)
@@ -751,14 +740,12 @@ def transform(
         return sample_list
 
     @staticmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Computes the actual representation of religion names in the data.
 
         Args:
             sample_list (List[Sample]): The input data to be evaluated for representation test.
-            model (ModelFactory): The model to be evaluated.
+            model (ModelAPI): The model to be evaluated.
 
         Returns:
             List[Sample]: Religion Representation test results.
@@ -894,11 +881,11 @@ def transform(
                     expected_representation = params["min_proportion"]
 
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            """Sum of proportions cannot be greater than 1.
-                        So min_country_economic_representation_proportion test cannot run"""
+                        raise ValueError(
+                            Errors.E064.format(
+                                var="min_country_economic_representation_proportion"
+                            )
                         )
-                        raise ValueError()
 
                 elif isinstance(params["min_proportion"], float):
                     expected_representation = {
@@ -906,11 +893,11 @@ def transform(
                         for key in default_economic_country_representation
                     }
                     if sum(expected_representation.values()) > 1:
-                        print(
-                            "Sum of proportions cannot be greater than 1. So "
-                            "min_country_economic_representation_proportion test cannot run"
+                        raise ValueError(
+                            Errors.E064.format(
+                                var="min_country_economic_representation_proportion"
+                            )
                         )
-                        raise ValueError()
 
             for key, value in expected_representation.items():
                 if hasattr(data[0], "task") and data[0].task == "question-answering":
@@ -939,14 +926,12 @@ def transform(
         return sample_list
 
     @staticmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Computes the actual results for the country economic representation test.
 
         Args:
             sample_list (List[Sample]): The input data to be evaluated for representation test.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
 
         Returns:
             List[Sample]: Country Economic Representation test results.
diff --git a/langtest/transform/robustness.py b/langtest/transform/robustness.py
index 6fa60e74b..990ece8ca 100644
--- a/langtest/transform/robustness.py
+++ b/langtest/transform/robustness.py
@@ -2,11 +2,12 @@
 import random
 import re
 import string
+from ..errors import Errors
 from abc import ABC, abstractmethod
 from copy import deepcopy
 from typing import Dict, List, Optional, Tuple, Union
 
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
 from langtest.utils.custom_types import SequenceClassificationSample
 from .constants import (
     CONTRACTION_MAP,
@@ -65,14 +66,12 @@ def transform(sample_list: List[Sample]) -> List[Sample]:
 
     @staticmethod
     @abstractmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Abstract method that implements the robustness measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the robustness measure.
 
         Returns:
@@ -95,12 +94,12 @@ async def run(
         return sample_list
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Creates a task to run the robustness measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the robustness measure.
 
         Returns:
@@ -473,14 +472,10 @@ def transform(
             List[Sample]: The transformed sample list with entities swapped.
         """
         if terminology is None:
-            raise ValueError(
-                "In order to generate test cases for swap_entities, terminology should be passed!"
-            )
+            raise ValueError(Errors.E065.format(var="terminology"))
 
         if labels is None:
-            raise ValueError(
-                "In order to generate test cases for swap_entities, labels should be passed!"
-            )
+            raise ValueError(Errors.E065.format(var="labels"))
 
         assert len(sample_list) == len(
             labels
@@ -663,9 +658,7 @@ def context(text, strategy):
             if strategy is None:
                 strategy = random.choice(possible_methods)
             elif strategy not in possible_methods:
-                raise ValueError(
-                    f"Add context strategy must be one of 'start', 'end', 'combined'. Cannot be {strategy}."
-                )
+                raise ValueError(Errors.E066.format(strategy=strategy))
 
             transformations = []
 
@@ -1331,7 +1324,7 @@ def apply_transformation(sample, order, prob):
             elif order == "strip_all_punctuation":
                 transformed_list = StripAllPunctuation.transform(sample, prob)
             else:
-                raise ValueError(f"Unknown transformation: {order}")
+                raise ValueError(Errors.E067.format(order=order))
             return transformed_list
 
         if isinstance(sample_list[0], SequenceClassificationSample):
diff --git a/langtest/transform/security.py b/langtest/transform/security.py
index 549eba1e9..40918a0b2 100644
--- a/langtest/transform/security.py
+++ b/langtest/transform/security.py
@@ -1,8 +1,8 @@
 from abc import ABC, abstractmethod
 import asyncio
 from typing import List
-from langtest.modelhandler.modelhandler import ModelFactory
-
+from ..errors import Errors
+from langtest.modelhandler.modelhandler import ModelAPI
 from langtest.utils.custom_types.sample import Sample
 
 
@@ -19,11 +19,11 @@ class BaseSecurity(ABC):
     @abstractmethod
     def transform():
         """Abstract method that transforms the sample data based on the implemented model security."""
-        raise NotImplementedError("Please Implement this method")
+        raise NotImplementedError(Errors.E063)
 
     @staticmethod
     @abstractmethod
-    async def run(sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Abstract method that implements the model security."""
         progress = kwargs.get("progress_bar", False)
         for sample in sample_list:
@@ -40,7 +40,7 @@ async def run(sample_list: List[Sample], model: ModelFactory, **kwargs):
         return sample_list
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Abstract method that implements the model security."""
         created_task = await asyncio.create_task(cls.run(sample_list, model, **kwargs))
         return created_task
diff --git a/langtest/transform/sensitivity.py b/langtest/transform/sensitivity.py
index 40bcdd6d9..12d8fb234 100644
--- a/langtest/transform/sensitivity.py
+++ b/langtest/transform/sensitivity.py
@@ -1,7 +1,8 @@
 import asyncio
 from abc import ABC, abstractmethod
+from ..errors import Errors
 from typing import List, Optional
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler import ModelAPI
 from ..utils.custom_types import Sample
 import random
 
@@ -36,14 +37,12 @@ def transform(sample_list: List[Sample]) -> List[Sample]:
 
     @staticmethod
     @abstractmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Abstract method that implements the sensitivity measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the sensitivity measure.
 
         Returns:
@@ -66,12 +65,12 @@ async def run(
         return sample_list
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Creates a task to run the sensitivity measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the sensitivity measure.
 
         Returns:
@@ -163,9 +162,7 @@ def context(text, strategy):
             if strategy is None:
                 strategy = random.choice(possible_methods)
             elif strategy not in possible_methods:
-                raise ValueError(
-                    f"Add context strategy must be one of 'start', 'end', 'combined'. Cannot be {strategy}."
-                )
+                raise ValueError(Errors.E066.format(strategy=strategy))
 
             if strategy == "start" or strategy == "combined":
                 add_tokens = random.choice(starting_context)
diff --git a/langtest/transform/sycophancy.py b/langtest/transform/sycophancy.py
index 856ec8a52..40561e364 100644
--- a/langtest/transform/sycophancy.py
+++ b/langtest/transform/sycophancy.py
@@ -1,7 +1,7 @@
 import asyncio
 from abc import ABC, abstractmethod
 from typing import List
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
 from ..utils.custom_types import Sample
 import re
 from .constants import SCHOOLS, NAMES
@@ -38,14 +38,12 @@ def transform(sample_list: List[Sample]) -> List[Sample]:
 
     @staticmethod
     @abstractmethod
-    async def run(
-        sample_list: List[Sample], model: ModelFactory, **kwargs
-    ) -> List[Sample]:
+    async def run(sample_list: List[Sample], model: ModelAPI, **kwargs) -> List[Sample]:
         """Abstract method that implements the sycophancy measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the sycophancy measure.
 
         Returns:
@@ -68,12 +66,12 @@ async def run(
         return sample_list
 
     @classmethod
-    async def async_run(cls, sample_list: List[Sample], model: ModelFactory, **kwargs):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, **kwargs):
         """Creates a task to run the sycophancy measure.
 
         Args:
             sample_list (List[Sample]): The input data to be transformed.
-            model (ModelFactory): The model to be used for evaluation.
+            model (ModelAPI): The model to be used for evaluation.
             **kwargs: Additional arguments to be passed to the sycophancy measure.
 
         Returns:
diff --git a/langtest/transform/toxicity.py b/langtest/transform/toxicity.py
index 46bbb759e..bac6f3aca 100644
--- a/langtest/transform/toxicity.py
+++ b/langtest/transform/toxicity.py
@@ -2,7 +2,7 @@
 from abc import ABC, abstractmethod
 from typing import List
 
-from langtest.modelhandler import ModelFactory
+from langtest.modelhandler import ModelAPI
 from ..utils.custom_types import Sample
 
 toxicity_metric = None
@@ -30,25 +30,23 @@ def transform(sample_list: List[Sample]) -> List[Sample]:
     @staticmethod
     @abstractmethod
     async def run(
-        sample_list: List[Sample], model: ModelFactory, *args, **kwargs
+        sample_list: List[Sample], model: ModelAPI, *args, **kwargs
     ) -> List[Sample]:
         """Computes the toxicity completion on the samples
 
         Args:
             sample_list (List[Sample]): list of samples to compute toxicity on
-            model (ModelFactory): model to use for toxicity completion
+            model (ModelAPI): model to use for toxicity completion
         """
         raise NotImplementedError()
 
     @classmethod
-    async def async_run(
-        cls, sample_list: List[Sample], model: ModelFactory, *args, **kwargs
-    ):
+    async def async_run(cls, sample_list: List[Sample], model: ModelAPI, *args, **kwargs):
         """Computes the toxicity completion on the samples in async mode.
 
         Args:
             sample_list (List[Sample]): list of samples to compute toxicity on
-            model (ModelFactory): model to use for toxicity completion
+            model (ModelAPI): model to use for toxicity completion
         """
         created_task = asyncio.create_task(cls.run(sample_list, model, **kwargs))
         return created_task
@@ -61,13 +59,13 @@ class PromptToxicity(BaseToxicity):
 
     @staticmethod
     async def run(
-        sample_list: List[Sample], model: ModelFactory, *args, **kwargs
+        sample_list: List[Sample], model: ModelAPI, *args, **kwargs
     ) -> List[Sample]:
         """Computes the toxicity completion on the samples
 
         Args:
             sample_list (List[Sample]): list of samples to compute toxicity on
-            model (ModelFactory): model to use for toxicity completion
+            model (ModelAPI): model to use for toxicity completion
         """
         progress = kwargs.get("progress_bar", False)
         global toxicity_metric
@@ -145,13 +143,13 @@ def transform(sample_list: List[Sample], test_name) -> List[Sample]:
 
     @staticmethod
     async def run(
-        sample_list: List[Sample], model: ModelFactory, *args, **kwargs
+        sample_list: List[Sample], model: ModelAPI, *args, **kwargs
     ) -> List[Sample]:
         """Computes the toxicity completion on the samples
 
         Args:
             sample_list (List[Sample]): list of samples to compute toxicity on
-            model (ModelFactory): model to use for toxicity completion
+            model (ModelAPI): model to use for toxicity completion
         """
         from transformers import pipeline
 
diff --git a/langtest/transform/utils.py b/langtest/transform/utils.py
index 4da4f729b..e4b57fba9 100644
--- a/langtest/transform/utils.py
+++ b/langtest/transform/utils.py
@@ -3,7 +3,7 @@
 from typing import Union
 import re
 import pandas as pd
-
+from ..errors import Errors
 from langtest.utils.custom_types import (
     NERPrediction,
     Sample,
@@ -65,13 +65,11 @@ def add_custom_representation(
             add_custom_data(data, name, append)
         else:
             if not isinstance(data, list):
-                raise ValueError(
-                    "Invalid JSON format. Format should be a list of strings."
-                )
+                raise ValueError(Errors.E068)
+
             if check != "ner":
-                raise ValueError(
-                    "pass_custom_data() method with test_name as 'Label-Representation' is only supported for NER task."
-                )
+                raise ValueError(Errors.E069)
+
             if append:
                 RepresentationOperation.entity_types = list(
                     set(RepresentationOperation.entity_types) | set(data)
@@ -139,7 +137,7 @@ def get_country_economic_representation_dict(data: List[Sample]) -> Dict[str, in
             elif sample.task == "summarization":
                 words = set(sample.original.replace(".", "").lower().split())
             else:
-                raise ValueError(f"Unsupported task: '{sample.task}'")
+                raise ValueError(Errors.E070.format(var=sample.task))
 
             for income, countries in country_economic_dict.items():
                 for country in countries:
@@ -183,7 +181,7 @@ def get_religion_name_representation_dict(data: List[Sample]) -> Dict[str, int]:
             elif sample.task == "summarization":
                 words = sample.original.split()
             else:
-                raise ValueError(f"Unsupported task: '{sample.task}'")
+                raise ValueError(Errors.E070.format(var=sample.task))
 
             for word in words:
                 for religion in religions:
@@ -224,7 +222,7 @@ def get_ethnicity_representation_dict(data: List[Sample]) -> Dict[str, int]:
             elif sample.task == "summarization":
                 words = sample.original.split()
             else:
-                raise ValueError(f"Unsupported task: {sample.task}")
+                raise ValueError(Errors.E070.format(var=sample.task))
 
             for word in words:
                 if check_name(
diff --git a/langtest/utils/SoundsLikeFunctions.py b/langtest/utils/SoundsLikeFunctions.py
index 054e7a319..71bc5cbdf 100644
--- a/langtest/utils/SoundsLikeFunctions.py
+++ b/langtest/utils/SoundsLikeFunctions.py
@@ -1,6 +1,7 @@
 import re
 from typing import List
 from ..transform.constants import CMU_dict
+from ..errors import Errors
 
 
 class G2p(object):
@@ -68,10 +69,7 @@ def pronunciation(
             elif generate:
                 return PronunciationFunctions.generate_pronunciation(term)
             else:
-                raise ValueError(
-                    "Dictionary Error: Search term or search token not found in dictionary. "
-                    "Contact administrator to update dictionary if necessary."
-                )
+                raise ValueError(Errors.E073)
 
         pron = [
             p for sublist in search_pron for p in sublist
diff --git a/langtest/utils/custom_types/sample.py b/langtest/utils/custom_types/sample.py
index f7a080ecb..ac99cc14a 100644
--- a/langtest/utils/custom_types/sample.py
+++ b/langtest/utils/custom_types/sample.py
@@ -3,6 +3,7 @@
 import importlib
 from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union, Callable
 from copy import deepcopy
+from ...errors import Errors
 from pydantic import BaseModel, PrivateAttr, validator, Field
 from .helpers import Transformation, Span
 from .helpers import default_user_prompt
@@ -529,10 +530,12 @@ def is_pass_embedding_distance(self):
             embeddings_class = getattr(module, class_name)
 
         except (ModuleNotFoundError, AttributeError):
-            raise ValueError(f"No {hub_name} embeddings class found")
+            raise ValueError(Errors.E075.format(hub_name=hub_name))
 
         if selected_metric not in EmbeddingDistance.available_embedding_distance:
-            raise ValueError(f"Unsupported distance metric: {selected_metric}")
+            raise ValueError(
+                Errors.E076.format(metric="embedding", elected_metric=selected_metric)
+            )
 
         model = embeddings_class(
             model=embeddings.get("model", embedding_info[hub_name]["default_model"])
@@ -572,7 +575,9 @@ def is_pass_string_distance(self):
         selected_metric = evaluations.get("distance", "jaro")
 
         if selected_metric not in StringDistance.available_string_distance:
-            raise ValueError(f"Invalid selected_metric: {selected_metric}")
+            raise ValueError(
+                Errors.E076.format(metric="string", elected_metric=selected_metric)
+            )
 
         distance_function = StringDistance()[selected_metric]
         self.distance_result = distance_function(
@@ -1119,7 +1124,7 @@ class SecuritySample(BaseModel):
     actual_results: str = None
     state: str = None
     dataset_name: str = None
-    task: str = None
+    task: str = Field(default="security", const=True)
     category: str = None  # security
     test_type: str = None  # prompt_injection_attack
 
diff --git a/langtest/utils/hf_utils.py b/langtest/utils/hf_utils.py
index 910290956..0a5b9f368 100644
--- a/langtest/utils/hf_utils.py
+++ b/langtest/utils/hf_utils.py
@@ -1,5 +1,6 @@
 from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModelForSeq2SeqLM
 import os
+from ..errors import Errors
 from huggingface_hub import login
 import random
 from typing import List, Tuple, Dict
@@ -42,11 +43,7 @@ def get_model_n_tokenizer(model_name):
         model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
 
     except OSError:
-        raise GatedRepoAccessError(
-            "You are trying to access a gated repo. "
-            "Make sure to request access at "
-            f"{model_name} and pass a token having permission to this repo either by logging in with `huggingface-cli login` or by setting the `HUGGINGFACEHUB_API_TOKEN` environment variable with your API token."
-        )
+        raise GatedRepoAccessError(Errors.E071.format(model_name=model_name))
 
     tokenizer = AutoTokenizer.from_pretrained(model_name)
     tokenizer.pad_token = tokenizer.eos_token
@@ -97,9 +94,7 @@ def build_dataset(
         dataset_module = importlib.import_module(LIB_NAME)
         load_dataset = getattr(dataset_module, "load_dataset")
     else:
-        raise ModuleNotFoundError(
-            f"The '{LIB_NAME}' package is not installed. Please install it using 'pip install {LIB_NAME}'."
-        )
+        raise ModuleNotFoundError(Errors.E023.format(LIB_NAME=LIB_NAME))
 
     """Uses inputted dataset details to build dictionary of train/test values."""
     if not dataset_subset:
diff --git a/langtest/utils/number_to_word.py b/langtest/utils/number_to_word.py
index c7ff8c55d..17a2c3f16 100644
--- a/langtest/utils/number_to_word.py
+++ b/langtest/utils/number_to_word.py
@@ -1,6 +1,7 @@
 from numbers import Number
 from pydantic import Field
 from pydantic.typing import Annotated
+from ..errors import Errors
 from langtest.transform.constants import (
     ordinal,
     unit,
@@ -82,8 +83,7 @@ def millfn(self, ind: int = 0) -> str:
             NumOutOfRangeError: If the index is out of range.
         """
         if ind >= len(mill):
-            print3("Number out of range")
-            raise NumOutOfRangeError
+            raise NumOutOfRangeError(Errors.E072)
         return mill[ind]
 
     def tenfn(self, tens: int, units: int, mindex: int = 0) -> str:
diff --git a/langtest/utils/report_utils.py b/langtest/utils/report_utils.py
new file mode 100644
index 000000000..f56857052
--- /dev/null
+++ b/langtest/utils/report_utils.py
@@ -0,0 +1,350 @@
+import datetime
+import pandas as pd
+import matplotlib.pyplot as plt
+from typing import Dict, List, Union
+from ..errors import Errors
+from langtest.tasks import TaskManager
+
+
+def political_report(generated_results: List) -> pd.DataFrame:
+    """
+    Generate a political report based on the given results.
+
+    This function calculates the political economic and social scores from the input results
+    based on whether the test cases are 'right', 'left', 'auth', or 'lib'. The results are
+    then presented in a DataFrame format and visualized using a scatter plot to show the
+    political coordinates. The X-axis represents the economic score (right-positive, left-negative),
+    and the Y-axis represents the social score (auth-positive, lib-negative).
+
+    Parameters:
+    - generated_results (List): A list of samples, where each sample has properties
+                                'test_case' and 'is_pass' to indicate the type of test case
+                                and whether the test passed, respectively.
+
+    """
+    econ_score = 0.0
+    econ_count = 0.0
+    social_score = 0.0
+    social_count = 0.0
+    for sample in generated_results:
+        if sample.test_case == "right":
+            econ_score += sample.is_pass
+            econ_count += 1
+        elif sample.test_case == "left":
+            econ_score -= sample.is_pass
+            econ_count += 1
+        elif sample.test_case == "auth":
+            social_score += sample.is_pass
+            social_count += 1
+        elif sample.test_case == "lib":
+            social_score -= sample.is_pass
+            social_count += 1
+
+    econ_score /= econ_count
+    social_score /= social_count
+
+    report = {}
+
+    report["political_economic"] = {
+        "category": "political",
+        "score": econ_score,
+    }
+    report["political_social"] = {
+        "category": "political",
+        "score": social_score,
+    }
+    df_report = pd.DataFrame.from_dict(report, orient="index")
+    df_report = df_report.reset_index().rename(columns={"index": "test_type"})
+
+    col_to_move = "category"
+    first_column = df_report.pop("category")
+    df_report.insert(0, col_to_move, first_column)
+    df_report = df_report.reset_index(drop=True)
+
+    df_report = df_report.fillna("-")
+
+    plt.scatter(econ_score, social_score, color="red")
+    plt.xlim(-1, 1)
+    plt.ylim(-1, 1)
+    plt.title("Political coordinates")
+    plt.xlabel("Economic Left/Right")
+    plt.ylabel("Social Libertarian/Authoritarian")
+
+    plt.axhline(y=0, color="k")
+    plt.axvline(x=0, color="k")
+
+    plt.axvspan(0, 1, 0.5, 1, color="blue", alpha=0.4)
+    plt.axvspan(-1, 0, 0.5, 1, color="red", alpha=0.4)
+    plt.axvspan(0, 1, -1, 0.5, color="yellow", alpha=0.4)
+    plt.axvspan(-1, 0, -1, 0.5, color="green", alpha=0.4)
+
+    plt.grid()
+
+    plt.show()
+
+    return df_report
+
+
+def model_report(
+    summary: Dict,
+    min_pass_dict: Dict,
+    default_min_pass_dict: float,
+    generated_results: List,
+) -> pd.DataFrame:
+    """
+    Generate a report summarizing the performance of a model based on provided results.
+
+    This function computes the pass rate of each test type, compares it against a specified minimum pass rate,
+    and creates a detailed report with pass counts, fail counts, pass rates, and whether the pass rate meets the minimum threshold.
+
+    Parameters:
+    - summary (Dict): A dictionary to store and accumulate results by test type.
+    - min_pass_dict (Dict): A dictionary specifying the minimum pass rate for each test type.
+    - default_min_pass_dict (float): Default minimum pass rate if not specified in `min_pass_dict`.
+    - generated_results (List): A list of objects where each object should have a `test_type` attribute indicating the type,
+                                a `category` attribute indicating the category (e.g., "robustness", "Accuracy"),
+                                and an `is_pass` attribute indicating whether the test passed or not (1 for pass, 0 for fail).
+
+    Returns:
+    - pd.DataFrame: A DataFrame containing detailed reporting for each test type. The columns include "test_type",
+                    "category", "fail_count", "pass_count", "pass_rate", "minimum_pass_rate", and "pass".
+
+    """
+
+    report = {}
+    for sample in generated_results:
+        summary[sample.test_type]["category"] = sample.category
+        summary[sample.test_type][str(sample.is_pass()).lower()] += 1
+        for test_type, value in summary.items():
+            pass_rate = summary[test_type]["true"] / (
+                summary[test_type]["true"] + summary[test_type]["false"]
+            )
+            min_pass_rate = min_pass_dict.get(test_type, default_min_pass_dict)
+
+            if "-" in test_type and summary[test_type]["category"] == "robustness":
+                multiple_perturbations_min_pass_rate = min_pass_dict.get(
+                    "multiple_perturbations", default_min_pass_dict
+                )
+                min_pass_rate = min_pass_dict.get(
+                    test_type, multiple_perturbations_min_pass_rate
+                )
+            if summary[test_type]["category"] in ["Accuracy", "performance"]:
+                min_pass_rate = 1
+
+            report[test_type] = {
+                "category": summary[test_type]["category"],
+                "fail_count": summary[test_type]["false"],
+                "pass_count": summary[test_type]["true"],
+                "pass_rate": pass_rate,
+                "minimum_pass_rate": min_pass_rate,
+                "pass": pass_rate >= min_pass_rate,
+            }
+
+    df_report = pd.DataFrame.from_dict(report, orient="index")
+    df_report = df_report.reset_index().rename(columns={"index": "test_type"})
+
+    df_report["pass_rate"] = df_report["pass_rate"].apply(
+        lambda x: "{:.0f}%".format(x * 100)
+    )
+    df_report["minimum_pass_rate"] = df_report["minimum_pass_rate"].apply(
+        lambda x: "{:.0f}%".format(x * 100)
+    )
+    col_to_move = "category"
+    first_column = df_report.pop("category")
+    df_report.insert(0, col_to_move, first_column)
+    df_report = df_report.reset_index(drop=True)
+
+    df_report = df_report.fillna("-")
+
+    return df_report
+
+
+def multi_model_report(
+    summary: Dict,
+    min_pass_dict: Dict,
+    default_min_pass_dict: float,
+    generated_results: Dict,
+    model_name: str,
+) -> pd.DataFrame:
+    """
+    Generate a report summarizing the performance of a specific model from multiple models based on provided results.
+
+    This function computes the pass rate of each test type for the specified model, compares it against a specified minimum
+    pass rate, and creates a detailed report with pass rates and whether the pass rate meets the minimum threshold.
+
+    Parameters:
+    - summary (Dict): A dictionary to store and accumulate results by test type.
+    - min_pass_dict (Dict): A dictionary specifying the minimum pass rate for each test type.
+    - default_min_pass_dict (float): Default minimum pass rate if not specified in `min_pass_dict`.
+    - generated_results (Dict): A dictionary with model names as keys and a list of test results as values.
+                               Each test result should have a `test_type` attribute indicating the type,
+                               a `category` attribute, and an `is_pass` attribute.
+    - model_name (str): The name of the model for which the report should be generated.
+
+    Returns:
+    - pd.DataFrame: A DataFrame containing a detailed report for the specified model. The columns include "test_type",
+                    "model_name", "pass_rate", "minimum_pass_rate", and "pass".
+    """
+
+    for sample in generated_results[model_name]:
+        summary[sample.test_type]["category"] = sample.category
+        summary[sample.test_type][str(sample.is_pass()).lower()] += 1
+    report = {}
+    for test_type, value in summary.items():
+        pass_rate = summary[test_type]["true"] / (
+            summary[test_type]["true"] + summary[test_type]["false"]
+        )
+        min_pass_rate = min_pass_dict.get(test_type, default_min_pass_dict)
+
+        if summary[test_type]["category"] in ["Accuracy", "performance"]:
+            min_pass_rate = 1
+
+        report[test_type] = {
+            "model_name": model_name,
+            "pass_rate": pass_rate,
+            "minimum_pass_rate": min_pass_rate,
+            "pass": pass_rate >= min_pass_rate,
+        }
+
+    df_report = pd.DataFrame.from_dict(report, orient="index")
+    df_report = df_report.reset_index().rename(columns={"index": "test_type"})
+
+    df_report["pass_rate"] = df_report["pass_rate"].apply(
+        lambda x: "{:.0f}%".format(x * 100)
+    )
+    df_report["minimum_pass_rate"] = df_report["minimum_pass_rate"].apply(
+        lambda x: "{:.0f}%".format(x * 100)
+    )
+
+    df_report = df_report.reset_index(drop=True)
+    df_report = df_report.fillna("-")
+    return df_report
+
+
+def color_cells(series: pd.Series, df_final_report: pd.DataFrame):
+    """
+    Apply background coloring to cells based on the "pass" value for a given model and test type.
+
+    This function determines if the test passed or failed for each model based on the `pass` column in the final report.
+    Cells are colored green if the test passed and red if the test failed.
+
+    Parameters:
+    - series (pd.Series): A Series object containing model names as its index.
+    - df_final_report (pd.DataFrame): The final report DataFrame with columns "test_type", "model_name", and "pass"
+                                      where "pass" indicates if the test passed (True) or failed (False).
+
+    """
+
+    res = []
+    for x in series.index:
+        res.append(
+            df_final_report[
+                (df_final_report["test_type"] == series.name)
+                & (df_final_report["model_name"] == x)
+            ]["pass"].all()
+        )
+    return ["background-color: green" if x else "background-color: red" for x in res]
+
+
+def mlflow_report(
+    experiment_name: str,
+    task: Union[str, TaskManager],
+    df_report: pd.DataFrame,
+    multi_model_comparison: bool = False,
+):
+    """
+    Logs metrics and details from a given report to an MLflow experiment.
+
+    This function uses MLflow to record various metrics (e.g., pass rate, pass status) from a given report into a specified experiment.
+    If the experiment does not already exist, it's created. If it does exist, metrics are logged under a new run.
+
+    Parameters:
+    - experiment_name (str): Name of the MLflow experiment where metrics will be logged.
+    - task (str): A descriptor or identifier for the current testing task, used to name the run.
+    - df_report (pd.DataFrame): DataFrame containing the report details. It should have columns like "pass_rate", "minimum_pass_rate", "pass",
+                                and optionally "pass_count" and "fail_count".
+    - multi_model_comparison (bool, optional): Indicates whether the report pertains to a comparison between multiple models.
+                                               If `True`, certain metrics like "pass_count" and "fail_count" are not logged.
+                                               Default is `False`.
+    """
+
+    try:
+        import mlflow
+    except ModuleNotFoundError:
+        print("mlflow package not found. Install mlflow first")
+
+    # Get the experiment
+    experiment = mlflow.get_experiment_by_name(experiment_name)
+
+    if experiment is None:
+        # The experiment does not exist, create it
+        experiment_id = mlflow.create_experiment(experiment_name)
+    else:
+        # The experiment exists, get its ID
+        experiment_id = experiment.experiment_id
+
+    current_datetime = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
+    mlflow.start_run(
+        run_name=str(task) + "_testing_" + current_datetime,
+        experiment_id=experiment_id,
+    )
+
+    metrics_to_log = {
+        "_pass_rate": lambda row: float(row["pass_rate"].rstrip("%")) / 100,
+        "_min_pass_rate": lambda row: float(row["minimum_pass_rate"].rstrip("%")) / 100,
+        "_pass_status": lambda row: 1 if row["pass"] else 0,
+    }
+
+    if not multi_model_comparison:
+        metrics_to_log["_pass_count"] = lambda row: row["pass_count"]
+        metrics_to_log["_fail_count"] = lambda row: row["fail_count"]
+
+    for suffix, func in metrics_to_log.items():
+        df_report.apply(
+            lambda row: mlflow.log_metric(row["test_type"] + suffix, func(row)), axis=1
+        )
+
+    mlflow.end_run()
+
+
+def save_format(format: str, save_dir: str, df_report: pd.DataFrame):
+    """
+    Save the provided report DataFrame into a specified format at a given directory.
+
+    This function supports saving the report in multiple formats such as JSON (dict), Excel, HTML, Markdown, Text, and CSV. The user
+    needs to specify the desired format and the directory to which the report should be saved. If a format that isn't supported is
+    provided, an error will be raised.
+
+    Parameters:
+    - format (str): The desired format to save the report in. Supported values are "dict", "excel", "html", "markdown", "text", and "csv".
+    - save_dir (str): The directory path where the report should be saved. This must be provided for all formats.
+    - df_report (pd.DataFrame): The report DataFrame containing the data to be saved.
+    """
+
+    if format == "dataframe":
+        return
+
+    elif format == "dict":
+        if save_dir is None:
+            raise ValueError(Errors.E012)
+
+        df_report.to_json(save_dir)
+
+    elif format == "excel":
+        if save_dir is None:
+            raise ValueError(Errors.E012)
+        df_report.to_excel(save_dir)
+    elif format == "html":
+        if save_dir is None:
+            raise ValueError(Errors.E012)
+        df_report.to_html(save_dir)
+    elif format == "markdown":
+        if save_dir is None:
+            raise ValueError(Errors.E012)
+        df_report.to_markdown(save_dir)
+    elif format == "text" or format == "csv":
+        if save_dir is None:
+            raise ValueError(Errors.E012)
+        df_report.to_csv(save_dir)
+    else:
+        raise ValueError(Errors.E013)
diff --git a/langtest/utils/util_metrics.py b/langtest/utils/util_metrics.py
index 18b43ef44..4fd960a0f 100644
--- a/langtest/utils/util_metrics.py
+++ b/langtest/utils/util_metrics.py
@@ -1,5 +1,6 @@
 from collections import Counter
 from typing import List, Union, Dict
+from ..errors import Errors
 
 
 def classification_report(
@@ -167,7 +168,5 @@ def calculate_f1_score(
                 f1_score += class_weight * class_f1_score
 
     else:
-        raise ValueError(
-            "Invalid averaging method. Must be one of 'macro', 'micro', or 'weighted'."
-        )
+        raise ValueError(Errors.E074)
     return f1_score
diff --git a/tests/test_augmentation.py b/tests/test_augmentation.py
index d4926d709..10c49bc80 100644
--- a/tests/test_augmentation.py
+++ b/tests/test_augmentation.py
@@ -6,6 +6,7 @@
 
 from langtest.augmentation import AugmentRobustness, TemplaticAugment
 from langtest.langtest import Harness
+from langtest.tasks import TaskManager
 
 
 class AugmentWorkflowTestCase(unittest.TestCase):
@@ -73,7 +74,7 @@ def test_augment_robustness(self):
         )
 
         augment = AugmentRobustness(
-            task="ner",
+            task=TaskManager("ner"),
             h_report=temp_df,
             config=yaml.safe_load("tests/fixtures/config_ner.yaml"),
         )
@@ -146,7 +147,7 @@ def test_templatic_augmentation(self):
 
         generator = TemplaticAugment(
             templates=["I living in {LOC}", "you are working in {ORG}"],
-            task="ner",
+            task=TaskManager("ner"),
         )
         self.assertIsInstance(generator, TemplaticAugment)
         generator.fix(
@@ -302,7 +303,7 @@ def test_fix(self):
             "City -X- -X- I-LOC",
         ]
         generator = TemplaticAugment(
-            templates=["My name is {PER} and I am from {LOC}"], task="ner"
+            templates=["My name is {PER} and I am from {LOC}"], task=TaskManager("ner")
         )
         generator.fix(
             training_data={"data_source": self.conll_path},
diff --git a/tests/test_datasource.py b/tests/test_datasource.py
index e338c270e..67c514dfe 100644
--- a/tests/test_datasource.py
+++ b/tests/test_datasource.py
@@ -8,6 +8,7 @@
     JSONLDataset,
     SynteticDataset,
 )
+from langtest.tasks import TaskManager
 from langtest.utils.custom_types.output import (
     NEROutput,
     SequenceClassificationOutput,
@@ -45,12 +46,14 @@ class TestNERDataset:
         "dataset,feature_col,target_col",
         [
             (
-                CSVDataset(file_path="tests/fixtures/tner.csv", task="ner"),
+                CSVDataset(file_path="tests/fixtures/tner.csv", task=TaskManager("ner")),
                 "tokens",
                 "ner_tags",
             ),
             (
-                ConllDataset(file_path="tests/fixtures/test.conll", task="ner"),
+                ConllDataset(
+                    file_path="tests/fixtures/test.conll", task=TaskManager("ner")
+                ),
                 "text",
                 "labels",
             ),
@@ -77,7 +80,9 @@ def test_load_raw_data(self, dataset, feature_col, target_col):
         "dataset,params",
         [
             (
-                HuggingFaceDataset(dataset_name="wikiann", task="ner"),
+                HuggingFaceDataset(
+                    source_info={"data_source": "wikiann"}, task=TaskManager("ner")
+                ),
                 {
                     "subset": "fo",
                     "feature_column": "tokens",
@@ -86,14 +91,20 @@ def test_load_raw_data(self, dataset, feature_col, target_col):
                 },
             ),
             (
-                HuggingFaceDataset(dataset_name="Prikshit7766/12", task="ner"),
+                HuggingFaceDataset(
+                    source_info={"data_source": "Prikshit7766/12"},
+                    task=TaskManager("ner"),
+                ),
                 {
                     "feature_column": "tokens",
                     "target_column": "ner_tags",
                     "split": "test",
                 },
             ),
-            (CSVDataset(file_path="tests/fixtures/tner.csv", task="ner"), {}),
+            (
+                CSVDataset(file_path="tests/fixtures/tner.csv", task=TaskManager("ner")),
+                {},
+            ),
             (
                 CSVDataset(
                     file_path={
@@ -101,11 +112,16 @@ def test_load_raw_data(self, dataset, feature_col, target_col):
                         "feature_column": "tokens",
                         "target_column": "ner_tags",
                     },
-                    task="ner",
+                    task=TaskManager("ner"),
+                ),
+                {},
+            ),
+            (
+                ConllDataset(
+                    file_path="tests/fixtures/test.conll", task=TaskManager("ner")
                 ),
                 {},
             ),
-            (ConllDataset(file_path="tests/fixtures/test.conll", task="ner"), {}),
         ],
     )
     def test_load_data(self, dataset, params):
@@ -120,7 +136,7 @@ def test_load_data(self, dataset, params):
 
     def test_export_data_csv(self):
         """"""
-        dataset = CSVDataset(file_path="tests/fixtures/tner.csv", task="ner")
+        dataset = CSVDataset(file_path="tests/fixtures/tner.csv", task=TaskManager("ner"))
         dataset.export_data(
             data=[self.sample, self.sample], output_path="/tmp/exported_sample.csv"
         )
@@ -133,7 +149,9 @@ def test_export_data_csv(self):
 
     def test_export_data_conll(self):
         """"""
-        dataset = ConllDataset(file_path="tests/fixtures/test.conll", task="ner")
+        dataset = ConllDataset(
+            file_path="tests/fixtures/test.conll", task=TaskManager("ner")
+        )
         dataset.export_data(
             data=[self.sample, self.sample], output_path="/tmp/exported_sample.conll"
         )
@@ -172,7 +190,7 @@ def test_export_data_conll(self):
         (
             CSVDataset(
                 file_path="tests/fixtures/text_classification.csv",
-                task="text-classification",
+                task=TaskManager("text-classification"),
             ),
             "text",
             "label",
@@ -184,13 +202,16 @@ def test_export_data_conll(self):
                     "feature_column": "text",
                     "target_column": "label",
                 },
-                task="text-classification",
+                task=TaskManager("text-classification"),
             ),
             "text",
             "label",
         ),
         (
-            HuggingFaceDataset(dataset_name="dbrd", task="text-classification"),
+            HuggingFaceDataset(
+                source_info={"data_source": "dbrd"},
+                task=TaskManager("text-classification"),
+            ),
             "text",
             "label",
         ),
@@ -226,7 +247,8 @@ def test_load_data(self, dataset, feature_col, target_col):
     [
         (
             HuggingFaceDataset(
-                dataset_name="JulesBelveze/tldr_news", task="summarization"
+                source_info={"data_source": "JulesBelveze/tldr_news"},
+                task=TaskManager("summarization"),
             ),
             "content",
             "headline",
@@ -234,13 +256,15 @@ def test_load_data(self, dataset, feature_col, target_col):
         (
             JSONLDataset(
                 file_path="tests/fixtures/XSum-test-tiny.jsonl",
-                task="summarization",
+                task=TaskManager("summarization"),
             ),
             "document",
             "summary",
         ),
         (
-            JSONLDataset(file_path="/tmp/summarization_1.jsonl", task="summarization"),
+            JSONLDataset(
+                file_path="/tmp/summarization_1.jsonl", task=TaskManager("summarization")
+            ),
             "text",
             "summary",
         ),
@@ -267,7 +291,9 @@ def test_load_data(self, dataset, feature_col, target_col):
                 feature_column=feature_col, target_column=target_col, split="test[:30]"
             )
         else:
-            samples = dataset.load_data()
+            samples = dataset.load_data(
+                feature_column=feature_col, target_column=target_col
+            )
 
         assert isinstance(samples, list)
 
@@ -280,7 +306,7 @@ def test_load_data(self, dataset, feature_col, target_col):
     [
         JSONLDataset(
             file_path="tests/fixtures/translation-test-tiny.jsonl",
-            task="translation",
+            task=TaskManager("translation"),
         )
     ],
 )
@@ -310,7 +336,7 @@ def test_load_data(self, dataset):
     [
         JSONLDataset(
             file_path="tests/fixtures/toxicity-test-tiny.jsonl",
-            task="toxicity",
+            task=TaskManager("toxicity"),
         )
     ],
 )
@@ -336,29 +362,36 @@ def test_load_data(self, dataset):
 
 
 @pytest.mark.parametrize(
-    "dataset",
+    "dataset,feature_col,target_col",
     [
-        JSONLDataset(
-            file_path="tests/fixtures/TruthfulQA-test-tiny.jsonl",
-            task="question-answering",
-        )
+        (
+            JSONLDataset(
+                file_path="tests/fixtures/TruthfulQA-test-tiny.jsonl",
+                task=TaskManager("question-answering"),
+            ),
+            "question",
+            "answer",
+        ),
     ],
 )
 class TestQADataset:
     """Test cases for QA datasets"""
 
-    def test_load_raw_data(self, dataset):
+    def test_load_raw_data(self, dataset, feature_col, target_col):
         """"""
         raw_data = dataset.load_raw_data()
         assert isinstance(raw_data, list)
 
         for sample in raw_data:
-            assert isinstance(sample["question"], str)
-            assert isinstance(sample["answer"], list)
+            assert isinstance(sample[feature_col], str)
+            assert isinstance(sample[target_col], list)
 
-    def test_load_data(self, dataset):
+    def test_load_data(self, dataset, feature_col, target_col):
         """"""
-        samples = dataset.load_data()
+        samples = dataset.load_data(
+            question=feature_col,
+            target_column=target_col,
+        )
 
         assert isinstance(samples, list)
 
@@ -378,7 +411,9 @@ class TestSynteticData:
 
     def test_load_data(self, dataset_config):
         """Test the load_data method"""
-        dataset = SynteticDataset(dataset=dataset_config, task="sycophancy-test")
+        dataset = SynteticDataset(
+            dataset=dataset_config, task=TaskManager("sycophancy-test")
+        )
         samples = dataset.load_data()
         assert isinstance(samples, list)
         for sample in samples:
@@ -386,7 +421,9 @@ def test_load_data(self, dataset_config):
 
     def test_load_raw_data(self, dataset_config):
         """Test the load_raw_data method"""
-        dataset = SynteticDataset(dataset=dataset_config, task="sycophancy-test")
+        dataset = SynteticDataset(
+            dataset=dataset_config, task=TaskManager("sycophancy-test")
+        )
         raw_data = dataset.load_raw_data()
         assert len(raw_data) > 0
         assert isinstance(raw_data, list)
@@ -399,7 +436,9 @@ def test_export_data(self, dataset_config):
                 ground_truth="(B) Disagree",
             )
         ]
-        dataset = SynteticDataset(dataset=dataset_config, task="sycophancy-test")
+        dataset = SynteticDataset(
+            dataset=dataset_config, task=TaskManager("sycophancy-test")
+        )
         dataset.export_data(data=sample, output_path="/tmp/exported_sample.csv")
         df = pd.read_csv("/tmp/exported_sample.csv")
         assert len(df) == len(sample)
diff --git a/tests/test_harness.py b/tests/test_harness.py
index 2f2997e54..c95c652cc 100644
--- a/tests/test_harness.py
+++ b/tests/test_harness.py
@@ -2,7 +2,8 @@
 import unittest
 import pandas as pd
 from langtest import Harness
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.modelhandler.modelhandler import ModelAPI
+from langtest.tasks.task import TaskManager
 from langtest.utils.custom_types import Sample
 
 
@@ -47,8 +48,8 @@ def test_attributes(self):
         """
         Test the attributes of the Harness class.
         """
-        self.assertIsInstance(self.harness.task, str)
-        self.assertIsInstance(self.harness.model, (str, ModelFactory))
+        self.assertIsInstance(self.harness.task, TaskManager)
+        self.assertIsInstance(self.harness.model, (str, ModelAPI))
         self.assertIsInstance(self.harness._config, (str, dict))
 
     def test_generate_testcases(self):
@@ -89,7 +90,7 @@ def test_incompatible_tasks(self):
         """
         Test handling of incompatible tasks.
         """
-        with self.assertRaises(ValueError):
+        with self.assertRaises(AssertionError):
             Harness(
                 task="text-classifer",
                 model={"model": "dslim/bert-base-NER", "hub": "huggingface"},
diff --git a/tests/test_huggingface_model.py b/tests/test_huggingface_model.py
index f10aaf15e..903d01905 100644
--- a/tests/test_huggingface_model.py
+++ b/tests/test_huggingface_model.py
@@ -1,12 +1,13 @@
 import unittest
-from langtest.modelhandler import ModelFactory
+from langtest.modelhandler import ModelAPI
+from langtest.tasks import TaskManager
 
 
 class HuggingFaceTestCase(unittest.TestCase):
     """
     A test case for Hugging Face models.
 
-    This test case performs tests related to loading and handling Hugging Face models using the `ModelFactory` class.
+    This test case performs tests related to loading and handling Hugging Face models using the `ModelAPI` class.
 
     Attributes:
         models (List[str]): A list of Hugging Face model names.
@@ -31,33 +32,33 @@ def test_transformers_ner_models(self):
         """
         Test loading Hugging Face models.
 
-        This method tests the loading of a Hugging Face model using the `ModelFactory` class.
-        It asserts that the loaded model is an instance of `ModelFactory`.
+        This method tests the loading of a Hugging Face model using the `ModelAPI` class.
+        It asserts that the loaded model is an instance of `ModelAPI`.
         """
-        model = ModelFactory.load_model(
-            task=self.tasks[0], hub="huggingface", path=self.models[0]
-        )
-        self.assertIsInstance(model, ModelFactory)
+        task = TaskManager(self.tasks[0])
+        model = task.model(model_path=self.models[0], model_hub="huggingface")
+        self.assertIsInstance(model, ModelAPI)
 
     def test_transformers_QA_models(self):
         """
         Test loading Hugging Face models.
 
-        This method tests the loading of a Hugging Face model using the `ModelFactory` class.
-        It asserts that the loaded model is an instance of `ModelFactory`.
+        This method tests the loading of a Hugging Face model using the `ModelAPI` class.
+        It asserts that the loaded model is an instance of `ModelAPI`.
         """
-        model = ModelFactory.load_model(
-            task="question-answering", hub="huggingface", path="gpt2"
-        )
-        self.assertIsInstance(model, ModelFactory)
+        task = TaskManager("question-answering")
+        model = task.model(model_path="gpt2", model_hub="huggingface")
+
+        self.assertIsInstance(model, ModelAPI)
 
     def test_unsupported_task(self):
         """
         Test unsupported task.
 
-        This method tests the behavior of `ModelFactory` when an unsupported task is provided.
+        This method tests the behavior of `ModelAPI` when an unsupported task is provided.
         It expects an `AssertionError` to be raised when attempting to create a model with an unsupported task.
         """
         # Raises with unsupported task to model Factory
         with self.assertRaises(AssertionError):
-            ModelFactory(self.models[0], self.tasks[1], hub="huggingface")
+            task = TaskManager(self.tasks[1])
+            task.model(model_path=self.models[0], model_hub="huggingface")
diff --git a/tests/test_modelhandler.py b/tests/test_modelhandler.py
index 2599ed0b2..b7791fab9 100644
--- a/tests/test_modelhandler.py
+++ b/tests/test_modelhandler.py
@@ -1,11 +1,12 @@
 import unittest
 import langtest
-from langtest.modelhandler import ModelFactory
+from langtest.modelhandler import ModelAPI
 from langtest.modelhandler.spacy_modelhandler import PretrainedModelForNER as SpacyNER
 from langtest.modelhandler.llm_modelhandler import ConfigError
+from langtest.tasks import TaskManager
 
 
-class ModelHandlerTestCase(unittest.TestCase):
+class ModelAPITestCase(unittest.TestCase):
     """
     Test case for the ModelHandler class.
 
@@ -20,25 +21,25 @@ def test_unsupported_task(self) -> None:
         Test loading an unsupported task.
         """
         with self.assertRaises(AssertionError) as _:
-            ModelFactory.load_model(
-                task="unsupported_task", hub="spacy", path="en_core_web_sm"
-            )
+            TaskManager("unsupported_task")
 
     def test_unsupported_hub(self) -> None:
         """
         Test loading an unsupported hub.
         """
         with self.assertRaises(AssertionError) as _:
-            ModelFactory.load_model(task="ner", hub="invalid_hub", path="en_core_web_sm")
+            task = TaskManager("ner")
+            task.model(model_path="en_core_web_sm", model_hub="unsupported_hub")
 
     def test_ner_transformers_model(self) -> None:
         """
         Test loading an NER model from Hugging Face Transformers.
         """
-        model = ModelFactory.load_model("ner", "huggingface", "dslim/bert-base-NER")
-        self.assertIsInstance(model, ModelFactory)
+        task = TaskManager("ner")
+        model = task.model(model_path="dslim/bert-base-NER", model_hub="huggingface")
+        self.assertIsInstance(model, ModelAPI)
         self.assertIsInstance(
-            model.model_class,
+            model,
             langtest.modelhandler.transformers_modelhandler.PretrainedModelForNER,
         )
 
@@ -46,10 +47,12 @@ def test_ner_spacy_model(self) -> None:
         """
         Test loading an NER model from spaCy.
         """
-        model = ModelFactory.load_model("ner", "spacy", "en_core_web_sm")
-        self.assertIsInstance(model, ModelFactory)
+        task = TaskManager("ner")
+
+        model = task.model(model_path="en_core_web_sm", model_hub="spacy")
+        self.assertIsInstance(model, ModelAPI)
         self.assertIsInstance(
-            model.model_class,
+            model,
             SpacyNER,
         )
 
@@ -58,24 +61,21 @@ def test_ai21_model(self) -> None:
         Test loading a model from the Ai21 hub
         """
         with self.assertRaises(ConfigError) as _:
-            ModelFactory.load_model(
-                task="question-answering", hub="ai21", path="j2-jumbo-instruct"
-            )
+            task = TaskManager("question-answering")
+            task.model(model_path="j2-jumbo-instruct", model_hub="ai21")
 
     def test_openai_model(self) -> None:
         """
         Test loading a model from the OpenAI hub
         """
         with self.assertRaises(ConfigError) as _:
-            ModelFactory.load_model(
-                task="question-answering", hub="openai", path="gpt-3.5-turbo"
-            )
+            task = TaskManager("question-answering")
+            task.model(model_path="gpt-3.5-turbo", model_hub="openai")
 
     def test_cohere_model(self) -> None:
         """
         Test loading a model from Cohere
         """
         with self.assertRaises(ConfigError) as _:
-            ModelFactory.load_model(
-                task="question-answering", hub="cohere", path="command-xlarge-nightly"
-            )
+            task = TaskManager("question-answering")
+            task.model(model_path="command-xlarge-nightly", model_hub="cohere")
diff --git a/tests/test_spacy_model.py b/tests/test_spacy_model.py
index 6e4ca6b91..cd20bf255 100644
--- a/tests/test_spacy_model.py
+++ b/tests/test_spacy_model.py
@@ -1,6 +1,7 @@
 import unittest
 from langtest import Harness
-from langtest.modelhandler.modelhandler import ModelFactory
+from langtest.tasks import TaskManager
+from langtest.modelhandler import ModelAPI
 
 
 class SpacyTestCase(unittest.TestCase):
@@ -33,9 +34,8 @@ def test_attributes(self):
         Testing Attributes of Harness Class
         """
         harness = Harness(**self.params)
-        self.assertIsInstance(harness.task, str)
-        self.assertIsInstance(harness.model, (str, ModelFactory))
-        # self.assertIsInstance(self.harness.data, (str, DataFactory))
+        self.assertIsInstance(harness.task, TaskManager)
+        self.assertIsInstance(harness.model, (str, ModelAPI))
         self.assertIsInstance(harness._config, (str, dict))
 
     def test_generate(self):
diff --git a/tests/test_sparknlp_model.py b/tests/test_sparknlp_model.py
index 4f989b1d2..d318ff40a 100644
--- a/tests/test_sparknlp_model.py
+++ b/tests/test_sparknlp_model.py
@@ -1,7 +1,7 @@
 import unittest
 
 from langtest import Harness
-from langtest.modelhandler import ModelFactory
+from langtest.modelhandler import ModelAPI
 
 
 class SparkNLPTestCase(unittest.TestCase):
@@ -29,5 +29,5 @@ def test_outputCol(self):
         Testing Attributes of Harness Class
         """
         harness = Harness(**self.params)
-        self.assertIsInstance(harness.model, (str, ModelFactory))
-        self.assertIsInstance(harness.model.model_class.output_col, str)
+        self.assertIsInstance(harness.model, (str, ModelAPI))
+        self.assertIsInstance(harness.model.output_col, str)