novastack-core 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. novastack_core-0.1.0/.gitignore +85 -0
  2. novastack_core-0.1.0/PKG-INFO +37 -0
  3. novastack_core-0.1.0/README.md +9 -0
  4. novastack_core-0.1.0/novastack/core/__init__.py +7 -0
  5. novastack_core-0.1.0/novastack/core/bridge/__init__.py +0 -0
  6. novastack_core-0.1.0/novastack/core/bridge/pydantic.py +69 -0
  7. novastack_core-0.1.0/novastack/core/components.py +34 -0
  8. novastack_core-0.1.0/novastack/core/document/__init__.py +3 -0
  9. novastack_core-0.1.0/novastack/core/document/base.py +106 -0
  10. novastack_core-0.1.0/novastack/core/embeddings/__init__.py +6 -0
  11. novastack_core-0.1.0/novastack/core/embeddings/base.py +144 -0
  12. novastack_core-0.1.0/novastack/core/enums.py +13 -0
  13. novastack_core-0.1.0/novastack/core/evaluation/__init__.py +9 -0
  14. novastack_core-0.1.0/novastack/core/evaluation/base.py +56 -0
  15. novastack_core-0.1.0/novastack/core/evaluation/context_similarity.py +173 -0
  16. novastack_core-0.1.0/novastack/core/guardrails/__init__.py +4 -0
  17. novastack_core-0.1.0/novastack/core/guardrails/base.py +27 -0
  18. novastack_core-0.1.0/novastack/core/guardrails/enums.py +26 -0
  19. novastack_core-0.1.0/novastack/core/guardrails/schemas.py +45 -0
  20. novastack_core-0.1.0/novastack/core/instrumentation/__init__.py +15 -0
  21. novastack_core-0.1.0/novastack/core/instrumentation/events/__init__.py +6 -0
  22. novastack_core-0.1.0/novastack/core/instrumentation/events/embedding.py +31 -0
  23. novastack_core-0.1.0/novastack/core/instrumentation/events/llm.py +76 -0
  24. novastack_core-0.1.0/novastack/core/instrumentation/events/loader.py +32 -0
  25. novastack_core-0.1.0/novastack/core/instrumentation/events/retrieval.py +34 -0
  26. novastack_core-0.1.0/novastack/core/instrumentation/events/text_chunker.py +32 -0
  27. novastack_core-0.1.0/novastack/core/instrumentation/span/__init__.py +3 -0
  28. novastack_core-0.1.0/novastack/core/llms/__init__.py +11 -0
  29. novastack_core-0.1.0/novastack/core/llms/base.py +114 -0
  30. novastack_core-0.1.0/novastack/core/llms/enums.py +14 -0
  31. novastack_core-0.1.0/novastack/core/llms/schemas.py +53 -0
  32. novastack_core-0.1.0/novastack/core/loaders/__init__.py +4 -0
  33. novastack_core-0.1.0/novastack/core/loaders/base.py +70 -0
  34. novastack_core-0.1.0/novastack/core/loaders/directory.py +138 -0
  35. novastack_core-0.1.0/novastack/core/observability/__init__.py +6 -0
  36. novastack_core-0.1.0/novastack/core/prompts/__init__.py +3 -0
  37. novastack_core-0.1.0/novastack/core/prompts/base.py +94 -0
  38. novastack_core-0.1.0/novastack/core/prompts/utils.py +64 -0
  39. novastack_core-0.1.0/novastack/core/retrievers/__init__.py +3 -0
  40. novastack_core-0.1.0/novastack/core/retrievers/base.py +59 -0
  41. novastack_core-0.1.0/novastack/core/text_chunkers/__init__.py +11 -0
  42. novastack_core-0.1.0/novastack/core/text_chunkers/base.py +95 -0
  43. novastack_core-0.1.0/novastack/core/text_chunkers/semantic.py +132 -0
  44. novastack_core-0.1.0/novastack/core/text_chunkers/sentence.py +102 -0
  45. novastack_core-0.1.0/novastack/core/text_chunkers/token.py +96 -0
  46. novastack_core-0.1.0/novastack/core/text_chunkers/utils.py +149 -0
  47. novastack_core-0.1.0/novastack/core/tools/__init__.py +5 -0
  48. novastack_core-0.1.0/novastack/core/tools/base.py +88 -0
  49. novastack_core-0.1.0/novastack/core/utils/__init__.py +6 -0
  50. novastack_core-0.1.0/novastack/core/utils/http/__init__.py +5 -0
  51. novastack_core-0.1.0/novastack/core/utils/http/authenticators/__init__.py +15 -0
  52. novastack_core-0.1.0/novastack/core/utils/retry/__init__.py +19 -0
  53. novastack_core-0.1.0/novastack/core/vector_stores/__init__.py +3 -0
  54. novastack_core-0.1.0/novastack/core/vector_stores/base.py +117 -0
  55. novastack_core-0.1.0/novastack/core/workflows/__init__.py +10 -0
  56. novastack_core-0.1.0/pyproject.toml +46 -0
@@ -0,0 +1,85 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ #IDE
10
+ .DS_Store
11
+ .idea
12
+ .vscode
13
+
14
+ # Distribution / packaging
15
+ .Python
16
+ build/
17
+ develop-eggs/
18
+ dist/
19
+ downloads/
20
+ eggs/
21
+ .eggs/
22
+ lib/
23
+ lib64/
24
+ parts/
25
+ sdist/
26
+ var/
27
+ wheels/
28
+ share/python-wheels/
29
+ *.egg-info/
30
+ .installed.cfg
31
+ *.egg
32
+ MANIFEST
33
+
34
+ # PyInstaller
35
+ # Usually these files are written by a python script from a template
36
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
37
+ *.manifest
38
+ *.spec
39
+
40
+ # Installer logs
41
+ pip-log.txt
42
+ pip-delete-this-directory.txt
43
+
44
+ # Unit test / coverage reports
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+ .coverage
49
+ .coverage.*
50
+ .cache
51
+ nosetests.xml
52
+ coverage.xml
53
+ *.cover
54
+ *.py,cover
55
+ .hypothesis/
56
+ .pytest_cache/
57
+ cover/
58
+
59
+ # Mkdocs documentation
60
+ docs/_build/
61
+ docs/api_reference/site/
62
+
63
+ # Ruff
64
+ .ruff_cache/
65
+
66
+ # PyBuilder
67
+ .pybuilder/
68
+ target/
69
+
70
+ # Jupyter Notebook
71
+ .ipynb_checkpoints
72
+
73
+ # pyenv
74
+ # For a library or package, you might want to ignore these files since the code is
75
+ # intended to run in multiple environments; otherwise, check them in:
76
+ .python-version
77
+
78
+ # Environments
79
+ .env
80
+ .venv
81
+ env/
82
+ venv/
83
+ ENV/
84
+ env.bak/
85
+ venv.bak/
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: novastack-core
3
+ Version: 0.1.0
4
+ Summary: Forge data pipelines, unleash smart AI applications.
5
+ Project-URL: Repository, https://github.com/novastack-project/novastack/tree/main/novastack-core
6
+ Author-email: Leonardo Furnielis <leonardofurnielis@outlook.com>
7
+ License: Apache-2.0
8
+ Keywords: AI,LLM,QA,RAG,data,observability,retrieval,semantic-search
9
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
10
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Requires-Python: <3.14,>=3.11
13
+ Requires-Dist: deprecated<2.0.0,>=1.3.1
14
+ Requires-Dist: httpx<1.0.0,>=0.28.1
15
+ Requires-Dist: nltk<4.0.0,>=3.9.2
16
+ Requires-Dist: novastack-instrumentation<0.2.0,>=0.1.0
17
+ Requires-Dist: novastack-utils<0.2.0,>=0.1.0
18
+ Requires-Dist: novastack-workflows<0.2.0,>=0.1.0
19
+ Requires-Dist: numpy<2.0.0,>=1.26.4
20
+ Requires-Dist: pydantic<3.0.0,>=2.12.5
21
+ Requires-Dist: tiktoken<0.13.0,>=0.12.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-asyncio<2.0.0,>=1.4.0; extra == 'dev'
24
+ Requires-Dist: pytest-mock<4.0.0,>=3.15.1; extra == 'dev'
25
+ Requires-Dist: pytest<10.0.0,>=9.1.1; extra == 'dev'
26
+ Requires-Dist: ruff<0.16.0,>=0.15.19; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Novastack Core
30
+
31
+ Core Python package providing essential abstractions and interfaces for building LLM applications, with a focus on Retrieval-Augmented Generation (RAG).
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install novastack-core
37
+ ```
@@ -0,0 +1,9 @@
1
+ # Novastack Core
2
+
3
+ Core Python package providing essential abstractions and interfaces for building LLM applications, with a focus on Retrieval-Augmented Generation (RAG).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install novastack-core
9
+ ```
@@ -0,0 +1,7 @@
1
+ from novastack.core.instrumentation import (
2
+ set_global_handler,
3
+ )
4
+
5
+ __all__ = [
6
+ "set_global_handler",
7
+ ]
File without changes
@@ -0,0 +1,69 @@
1
+ import pydantic
2
+ from pydantic import (
3
+ AnyUrl,
4
+ BaseModel,
5
+ BeforeValidator,
6
+ Field,
7
+ FilePath,
8
+ GetCoreSchemaHandler,
9
+ GetJsonSchemaHandler,
10
+ PlainSerializer,
11
+ PrivateAttr,
12
+ Secret,
13
+ SecretStr,
14
+ SerializationInfo,
15
+ SerializeAsAny,
16
+ SerializerFunctionWrapHandler,
17
+ StrictFloat,
18
+ StrictInt,
19
+ StrictStr,
20
+ TypeAdapter,
21
+ ValidationError,
22
+ ValidationInfo,
23
+ WithJsonSchema,
24
+ WrapSerializer,
25
+ computed_field,
26
+ create_model,
27
+ field_serializer,
28
+ field_validator,
29
+ model_serializer,
30
+ model_validator,
31
+ validate_call,
32
+ )
33
+ from pydantic.fields import FieldInfo
34
+ from pydantic.json_schema import JsonSchemaValue
35
+
36
+ __all__ = [
37
+ "pydantic",
38
+ "BaseModel",
39
+ "GetJsonSchemaHandler",
40
+ "GetCoreSchemaHandler",
41
+ "Field",
42
+ "PlainSerializer",
43
+ "PrivateAttr",
44
+ "model_serializer",
45
+ "model_validator",
46
+ "validate_call",
47
+ "field_validator",
48
+ "computed_field",
49
+ "create_model",
50
+ "StrictFloat",
51
+ "StrictInt",
52
+ "StrictStr",
53
+ "FieldInfo",
54
+ "ValidationInfo",
55
+ "TypeAdapter",
56
+ "ValidationError",
57
+ "WithJsonSchema",
58
+ "BeforeValidator",
59
+ "JsonSchemaValue",
60
+ "SerializeAsAny",
61
+ "WrapSerializer",
62
+ "field_serializer",
63
+ "Secret",
64
+ "SecretStr",
65
+ "AnyUrl",
66
+ "FilePath",
67
+ "SerializationInfo",
68
+ "SerializerFunctionWrapHandler",
69
+ ]
@@ -0,0 +1,34 @@
1
+ import json
2
+ from abc import abstractmethod
3
+ from typing import Any
4
+
5
+ from novastack.core.bridge.pydantic import BaseModel
6
+ from novastack.core.document import Document
7
+
8
+
9
+ class BaseComponent(BaseModel):
10
+ """Base component object."""
11
+
12
+ @classmethod
13
+ def class_name(cls) -> str:
14
+ """Return the class name for identification purposes."""
15
+ return "BaseComponent"
16
+
17
+ def to_dict(self, **kwargs: Any) -> dict[str, Any]:
18
+ data = self.model_dump(**kwargs)
19
+ data["class_name"] = self.class_name()
20
+ return data
21
+
22
+ def to_json(self, **kwargs: Any) -> str:
23
+ data = self.to_dict(**kwargs)
24
+ return json.dumps(data)
25
+
26
+
27
+ class TransformerComponent(BaseComponent):
28
+ """Abstract base class for document transformer components."""
29
+
30
+ @abstractmethod
31
+ def __call__(self, documents: list[Document]) -> list[Document]:
32
+ raise NotImplementedError(
33
+ f"{self.__class__.__name__} must implement the __call__() method."
34
+ )
@@ -0,0 +1,3 @@
1
+ from novastack.core.document.base import BaseDocument, Document, DocumentWithScore
2
+
3
+ __all__ = ["BaseDocument", "Document", "DocumentWithScore"]
@@ -0,0 +1,106 @@
1
+ import uuid
2
+ from abc import ABC, abstractmethod
3
+ from hashlib import sha256
4
+ from typing import Any
5
+
6
+ import numpy as np
7
+ from novastack.core.bridge.pydantic import Field, computed_field, field_validator
8
+ from novastack.core.components import BaseComponent
9
+
10
+
11
+ class BaseDocument(BaseComponent, ABC):
12
+ """Abstract base class defining the interface for retrievable documents."""
13
+
14
+ model_config = {"arbitrary_types_allowed": True, "validate_assignment": True}
15
+
16
+ id_: str = Field(
17
+ default_factory=lambda: str(uuid.uuid4()),
18
+ description="Unique Id of the document.",
19
+ )
20
+ metadata: dict[str, Any] = Field(
21
+ default_factory=dict,
22
+ description="A flat dictionary of metadata fields.",
23
+ )
24
+ embedding: list[float] | np.ndarray | None = Field(
25
+ default=None,
26
+ description="Embedding of the document.",
27
+ )
28
+
29
+ @field_validator("metadata", mode="before")
30
+ @classmethod
31
+ def _validate_metadata(cls, v) -> dict:
32
+ """Ensure metadata is always a dict."""
33
+ if v is None:
34
+ return {}
35
+ return v
36
+
37
+ @abstractmethod
38
+ def get_content(self) -> str:
39
+ """Get document content."""
40
+ raise NotImplementedError(
41
+ f"{self.__class__.__name__} must implement the get_content() method"
42
+ )
43
+
44
+ @property
45
+ @abstractmethod
46
+ def hash(self) -> str:
47
+ """Get document hash."""
48
+ raise NotImplementedError(
49
+ f"{self.__class__.__name__} must implement the get_content() method"
50
+ )
51
+
52
+
53
+ class Document(BaseDocument):
54
+ """Generic interface for data document."""
55
+
56
+ text: str = Field(default="", description="Text content of the document.")
57
+
58
+ @classmethod
59
+ def class_name(cls) -> str:
60
+ return "Document"
61
+
62
+ def get_content(self) -> str:
63
+ """Get the text content."""
64
+ return self.text
65
+
66
+ @computed_field
67
+ @property
68
+ def hash(self) -> str:
69
+ """Get document hash based on text content."""
70
+ return str(sha256(str(self.text).encode("utf-8", "surrogatepass")).hexdigest())
71
+
72
+
73
+ class DocumentWithScore(BaseComponent):
74
+ """Document with associated relevance score."""
75
+
76
+ model_config = {"arbitrary_types_allowed": True, "validate_assignment": True}
77
+
78
+ document: BaseDocument
79
+ score: float | None = Field(
80
+ default=None,
81
+ description="Relevance score for the document.",
82
+ )
83
+
84
+ @classmethod
85
+ def class_name(cls) -> str:
86
+ return "DocumentWithScore"
87
+
88
+ @property
89
+ def normalized_score(self) -> float:
90
+ """Get normalized score (0.0 if None)."""
91
+ return self.score if self.score is not None else 0.0
92
+
93
+ # #### pass through properties to BaseDocument ####
94
+ @property
95
+ def id_(self) -> str:
96
+ """Get document Id."""
97
+ return self.document.id_
98
+
99
+ @property
100
+ def metadata(self) -> dict:
101
+ """Get document metadata."""
102
+ return self.document.metadata
103
+
104
+ def get_content(self) -> str:
105
+ """Get document content."""
106
+ return self.document.get_content()
@@ -0,0 +1,6 @@
1
+ from novastack.core.embeddings.base import BaseEmbedding, Embedding
2
+
3
+ __all__ = [
4
+ "BaseEmbedding",
5
+ "Embedding",
6
+ ]
@@ -0,0 +1,144 @@
1
+ from abc import abstractmethod
2
+
3
+ import numpy as np
4
+ from novastack.core.bridge.pydantic import Field
5
+ from novastack.core.components import TransformerComponent
6
+ from novastack.core.document import Document
7
+ from novastack.core.enums import SimilarityMode
8
+ from novastack.core.instrumentation import DispatcherSpanMixin, get_dispatcher
9
+ from novastack.core.instrumentation.events.embedding import (
10
+ EmbeddingEndEvent,
11
+ EmbeddingStartEvent,
12
+ )
13
+ from novastack.core.utils import validate_enum
14
+
15
+ dispatcher = get_dispatcher(__name__)
16
+ Embedding = list[float]
17
+
18
+
19
+ def similarity(
20
+ embedding1: Embedding,
21
+ embedding2: Embedding,
22
+ mode: str = SimilarityMode.COSINE,
23
+ ) -> float:
24
+ """
25
+ Calculate similarity between two embeddings.
26
+
27
+ Args:
28
+ embedding1: First embedding vector
29
+ embedding2: Second embedding vector
30
+ mode: Similarity calculation mode (cosine, dot_product, or euclidean)
31
+ """
32
+ validate_enum(el=mode, el_name="mode", expected_enum=SimilarityMode)
33
+ # Validate embeddings are not empty
34
+ if len(embedding1) == 0 or len(embedding2) == 0:
35
+ raise ValueError("Embeddings cannot be empty")
36
+
37
+ # Validate embeddings have same dimension
38
+ if len(embedding1) != len(embedding2):
39
+ raise ValueError(
40
+ f"Embeddings must have same dimension. "
41
+ f"Got {len(embedding1)} and {len(embedding2)}"
42
+ )
43
+
44
+ if mode == SimilarityMode.EUCLIDEAN:
45
+ return -float(np.linalg.norm(np.array(embedding1) - np.array(embedding2)))
46
+
47
+ elif mode == SimilarityMode.DOT_PRODUCT:
48
+ return float(np.dot(embedding1, embedding2))
49
+
50
+ else:
51
+ # Cosine similarity calculation
52
+ X = np.array(embedding1)
53
+ Y = np.array(embedding2)
54
+ product = np.dot(X, Y)
55
+ norm = np.linalg.norm(X) * np.linalg.norm(Y)
56
+ return float(product / norm)
57
+
58
+
59
+ class BaseEmbedding(TransformerComponent, DispatcherSpanMixin):
60
+ """
61
+ Abstract base class defining the interface for embedding models.
62
+ """
63
+
64
+ model_config = {
65
+ "arbitrary_types_allowed": True,
66
+ "validate_assignment": True,
67
+ "validate_default": True,
68
+ }
69
+
70
+ model_name: str = Field(..., description="Name of the embedding model")
71
+
72
+ @classmethod
73
+ def class_name(cls) -> str:
74
+ return "BaseEmbedding"
75
+
76
+ @staticmethod
77
+ def similarity(
78
+ embedding1: Embedding,
79
+ embedding2: Embedding,
80
+ mode: str = SimilarityMode.COSINE,
81
+ ):
82
+ """Get embedding similarity."""
83
+ return similarity(embedding1, embedding2, mode)
84
+
85
+ @abstractmethod
86
+ def _get_text_embeddings(self, input: str | list[str]) -> list[Embedding]:
87
+ """Embed one or more text strings."""
88
+
89
+ @dispatcher.span
90
+ def get_text_embeddings(self, input: str | list[str]) -> list[Embedding]:
91
+ """
92
+ Embed one or more text strings.
93
+
94
+ Args:
95
+ input: Single text string or list of text strings to embed
96
+ """
97
+ config_dict = self.to_dict(exclude={"api_key"})
98
+ dispatcher.event(
99
+ EmbeddingStartEvent(
100
+ config_dict=config_dict,
101
+ )
102
+ )
103
+
104
+ embeddings = self._get_text_embeddings(input)
105
+
106
+ dispatcher.event(
107
+ EmbeddingEndEvent(
108
+ embeddings=embeddings,
109
+ )
110
+ )
111
+ return embeddings
112
+
113
+ @dispatcher.span
114
+ def get_document_embeddings(self, documents: list[Document]) -> list[Document]:
115
+ """
116
+ Embed a list of documents and assign the computed embeddings to the 'embedding' attribute.
117
+
118
+ Args:
119
+ documents (list[Document]): List of documents to compute embeddings.
120
+ """
121
+ config_dict = self.to_dict(exclude={"api_key"})
122
+ dispatcher.event(
123
+ EmbeddingStartEvent(
124
+ config_dict=config_dict,
125
+ )
126
+ )
127
+
128
+ texts = [document.get_content() for document in documents]
129
+ embeddings = self._get_text_embeddings(texts)
130
+
131
+ for document, embedding in zip(documents, embeddings):
132
+ document.embedding = embedding
133
+
134
+ config_dict = self.to_dict(exclude={"api_key"})
135
+
136
+ dispatcher.event(
137
+ EmbeddingEndEvent(
138
+ embeddings=embeddings,
139
+ )
140
+ )
141
+ return documents
142
+
143
+ def __call__(self, documents: list[Document]) -> list[Document]:
144
+ return self.get_document_embeddings(documents)
@@ -0,0 +1,13 @@
1
+ class SimilarityMode:
2
+ """
3
+ Describes possible similarity types.
4
+
5
+ Attributes:
6
+ COSINE (str): "cosine".
7
+ DOT_PRODUCT (str): "dot_product".
8
+ EUCLIDEAN (str): "euclidean".
9
+ """
10
+
11
+ COSINE = "cosine"
12
+ DOT_PRODUCT = "dot_product"
13
+ EUCLIDEAN = "euclidean"
@@ -0,0 +1,9 @@
1
+ from novastack.core.evaluation.base import BaseEvaluator
2
+ from novastack.core.evaluation.context_similarity import (
3
+ ContextSimilarityEvaluator,
4
+ )
5
+
6
+ __all__ = [
7
+ "BaseEvaluator",
8
+ "ContextSimilarityEvaluator",
9
+ ]
@@ -0,0 +1,56 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ from novastack.core.bridge.pydantic import Field, field_validator
5
+ from novastack.core.components import BaseComponent
6
+
7
+
8
+ class BaseEvaluator(BaseComponent, ABC):
9
+ """
10
+ Abstract base class defining the interface for evaluation metrics.
11
+
12
+ All evaluators should inherit from this class and implement the evaluate method.
13
+ """
14
+
15
+ model_config = {
16
+ "arbitrary_types_allowed": True,
17
+ "validate_assignment": True,
18
+ "validate_default": True,
19
+ }
20
+
21
+ score_threshold: float = Field(
22
+ default=0.8,
23
+ ge=0.0,
24
+ le=1.0,
25
+ description="Minimum required score for evaluation approval",
26
+ )
27
+
28
+ @field_validator("score_threshold")
29
+ @classmethod
30
+ def _validate_threshold(cls, v: float) -> float:
31
+ """Validate that threshold is within valid range."""
32
+ if not 0.0 <= v <= 1.0:
33
+ raise ValueError(f"score_threshold must be between 0.0 and 1.0, got: {v}")
34
+ return v
35
+
36
+ @classmethod
37
+ def class_name(cls) -> str:
38
+ return "BaseEvaluator"
39
+
40
+ @abstractmethod
41
+ def evaluate(
42
+ self,
43
+ query: str | None = None,
44
+ generated_text: str | None = None,
45
+ contexts: list[str] | None = None,
46
+ **kwargs: Any,
47
+ ) -> dict:
48
+ """
49
+ Evaluate the given inputs and return evaluation results.
50
+
51
+ This method should be implemented by all concrete evaluator classes.
52
+ The specific parameters will vary depending on the evaluation type.
53
+ """
54
+ raise NotImplementedError(
55
+ f"{self.__class__.__name__} must implement the evaluate() method"
56
+ )