validmind 2.8.10__py3-none-any.whl → 2.8.20__py3-none-any.whl
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.
- validmind/__init__.py +6 -5
- validmind/__version__.py +1 -1
- validmind/ai/test_descriptions.py +17 -11
- validmind/ai/utils.py +2 -2
- validmind/api_client.py +75 -32
- validmind/client.py +108 -100
- validmind/client_config.py +3 -3
- validmind/datasets/classification/__init__.py +7 -3
- validmind/datasets/credit_risk/lending_club.py +28 -16
- validmind/datasets/nlp/cnn_dailymail.py +10 -4
- validmind/datasets/regression/__init__.py +22 -5
- validmind/errors.py +17 -7
- validmind/input_registry.py +1 -1
- validmind/logging.py +44 -35
- validmind/models/foundation.py +2 -2
- validmind/models/function.py +10 -3
- validmind/template.py +30 -22
- validmind/test_suites/__init__.py +2 -2
- validmind/tests/_store.py +13 -4
- validmind/tests/comparison.py +65 -33
- validmind/tests/data_validation/ACFandPACFPlot.py +4 -1
- validmind/tests/data_validation/AutoMA.py +1 -1
- validmind/tests/data_validation/BivariateScatterPlots.py +5 -1
- validmind/tests/data_validation/BoxPierce.py +3 -1
- validmind/tests/data_validation/ClassImbalance.py +4 -2
- validmind/tests/data_validation/DatasetDescription.py +3 -24
- validmind/tests/data_validation/DescriptiveStatistics.py +1 -1
- validmind/tests/data_validation/DickeyFullerGLS.py +1 -1
- validmind/tests/data_validation/FeatureTargetCorrelationPlot.py +1 -1
- validmind/tests/data_validation/HighCardinality.py +5 -1
- validmind/tests/data_validation/HighPearsonCorrelation.py +1 -1
- validmind/tests/data_validation/IQROutliersBarPlot.py +5 -3
- validmind/tests/data_validation/IQROutliersTable.py +5 -2
- validmind/tests/data_validation/IsolationForestOutliers.py +5 -4
- validmind/tests/data_validation/JarqueBera.py +2 -2
- validmind/tests/data_validation/LJungBox.py +2 -2
- validmind/tests/data_validation/LaggedCorrelationHeatmap.py +1 -1
- validmind/tests/data_validation/MissingValues.py +14 -10
- validmind/tests/data_validation/MissingValuesBarPlot.py +3 -1
- validmind/tests/data_validation/MutualInformation.py +2 -1
- validmind/tests/data_validation/PearsonCorrelationMatrix.py +1 -1
- validmind/tests/data_validation/ProtectedClassesCombination.py +2 -0
- validmind/tests/data_validation/ProtectedClassesDescription.py +2 -2
- validmind/tests/data_validation/ProtectedClassesDisparity.py +9 -5
- validmind/tests/data_validation/ProtectedClassesThresholdOptimizer.py +10 -2
- validmind/tests/data_validation/RollingStatsPlot.py +2 -1
- validmind/tests/data_validation/ScoreBandDefaultRates.py +4 -2
- validmind/tests/data_validation/SeasonalDecompose.py +1 -1
- validmind/tests/data_validation/ShapiroWilk.py +2 -2
- validmind/tests/data_validation/Skewness.py +7 -6
- validmind/tests/data_validation/SpreadPlot.py +1 -1
- validmind/tests/data_validation/TabularCategoricalBarPlots.py +1 -1
- validmind/tests/data_validation/TabularDateTimeHistograms.py +1 -1
- validmind/tests/data_validation/TargetRateBarPlots.py +4 -1
- validmind/tests/data_validation/TimeSeriesFrequency.py +1 -1
- validmind/tests/data_validation/TimeSeriesOutliers.py +7 -2
- validmind/tests/data_validation/WOEBinPlots.py +1 -1
- validmind/tests/data_validation/WOEBinTable.py +1 -1
- validmind/tests/data_validation/ZivotAndrewsArch.py +5 -2
- validmind/tests/data_validation/nlp/CommonWords.py +1 -1
- validmind/tests/data_validation/nlp/Hashtags.py +1 -1
- validmind/tests/data_validation/nlp/LanguageDetection.py +1 -1
- validmind/tests/data_validation/nlp/Mentions.py +1 -1
- validmind/tests/data_validation/nlp/PolarityAndSubjectivity.py +5 -1
- validmind/tests/data_validation/nlp/Punctuations.py +1 -1
- validmind/tests/data_validation/nlp/Sentiment.py +3 -1
- validmind/tests/data_validation/nlp/TextDescription.py +1 -1
- validmind/tests/data_validation/nlp/Toxicity.py +1 -1
- validmind/tests/decorator.py +14 -11
- validmind/tests/load.py +38 -24
- validmind/tests/model_validation/BertScore.py +7 -1
- validmind/tests/model_validation/BleuScore.py +7 -1
- validmind/tests/model_validation/ClusterSizeDistribution.py +3 -1
- validmind/tests/model_validation/ContextualRecall.py +9 -1
- validmind/tests/model_validation/FeaturesAUC.py +1 -1
- validmind/tests/model_validation/MeteorScore.py +7 -1
- validmind/tests/model_validation/ModelPredictionResiduals.py +5 -1
- validmind/tests/model_validation/RegardScore.py +6 -1
- validmind/tests/model_validation/RegressionResidualsPlot.py +10 -1
- validmind/tests/model_validation/RougeScore.py +3 -1
- validmind/tests/model_validation/TimeSeriesPredictionWithCI.py +2 -0
- validmind/tests/model_validation/TimeSeriesPredictionsPlot.py +10 -2
- validmind/tests/model_validation/TimeSeriesR2SquareBySegments.py +6 -2
- validmind/tests/model_validation/TokenDisparity.py +5 -1
- validmind/tests/model_validation/ToxicityScore.py +2 -0
- validmind/tests/model_validation/embeddings/ClusterDistribution.py +1 -1
- validmind/tests/model_validation/embeddings/CosineSimilarityComparison.py +5 -1
- validmind/tests/model_validation/embeddings/CosineSimilarityDistribution.py +5 -1
- validmind/tests/model_validation/embeddings/CosineSimilarityHeatmap.py +5 -1
- validmind/tests/model_validation/embeddings/DescriptiveAnalytics.py +2 -0
- validmind/tests/model_validation/embeddings/EmbeddingsVisualization2D.py +5 -1
- validmind/tests/model_validation/embeddings/EuclideanDistanceComparison.py +6 -2
- validmind/tests/model_validation/embeddings/EuclideanDistanceHeatmap.py +3 -1
- validmind/tests/model_validation/embeddings/PCAComponentsPairwisePlots.py +4 -1
- validmind/tests/model_validation/embeddings/StabilityAnalysisKeyword.py +5 -1
- validmind/tests/model_validation/embeddings/StabilityAnalysisRandomNoise.py +5 -1
- validmind/tests/model_validation/embeddings/StabilityAnalysisSynonyms.py +5 -1
- validmind/tests/model_validation/embeddings/StabilityAnalysisTranslation.py +5 -1
- validmind/tests/model_validation/embeddings/TSNEComponentsPairwisePlots.py +6 -1
- validmind/tests/model_validation/ragas/AnswerCorrectness.py +5 -3
- validmind/tests/model_validation/ragas/AspectCritic.py +4 -1
- validmind/tests/model_validation/ragas/ContextEntityRecall.py +5 -3
- validmind/tests/model_validation/ragas/ContextPrecision.py +5 -3
- validmind/tests/model_validation/ragas/ContextPrecisionWithoutReference.py +5 -3
- validmind/tests/model_validation/ragas/ContextRecall.py +5 -3
- validmind/tests/model_validation/ragas/Faithfulness.py +5 -3
- validmind/tests/model_validation/ragas/NoiseSensitivity.py +1 -1
- validmind/tests/model_validation/ragas/ResponseRelevancy.py +5 -3
- validmind/tests/model_validation/ragas/SemanticSimilarity.py +5 -3
- validmind/tests/model_validation/sklearn/AdjustedMutualInformation.py +9 -9
- validmind/tests/model_validation/sklearn/AdjustedRandIndex.py +9 -9
- validmind/tests/model_validation/sklearn/CalibrationCurve.py +5 -2
- validmind/tests/model_validation/sklearn/ClassifierThresholdOptimization.py +28 -5
- validmind/tests/model_validation/sklearn/ClusterCosineSimilarity.py +5 -1
- validmind/tests/model_validation/sklearn/ClusterPerformanceMetrics.py +24 -14
- validmind/tests/model_validation/sklearn/CompletenessScore.py +8 -9
- validmind/tests/model_validation/sklearn/ConfusionMatrix.py +22 -3
- validmind/tests/model_validation/sklearn/FeatureImportance.py +6 -2
- validmind/tests/model_validation/sklearn/FowlkesMallowsScore.py +12 -9
- validmind/tests/model_validation/sklearn/HomogeneityScore.py +14 -9
- validmind/tests/model_validation/sklearn/HyperParametersTuning.py +4 -2
- validmind/tests/model_validation/sklearn/KMeansClustersOptimization.py +6 -1
- validmind/tests/model_validation/sklearn/MinimumAccuracy.py +12 -7
- validmind/tests/model_validation/sklearn/MinimumF1Score.py +12 -7
- validmind/tests/model_validation/sklearn/MinimumROCAUCScore.py +21 -6
- validmind/tests/model_validation/sklearn/OverfitDiagnosis.py +11 -3
- validmind/tests/model_validation/sklearn/PermutationFeatureImportance.py +5 -1
- validmind/tests/model_validation/sklearn/PopulationStabilityIndex.py +5 -1
- validmind/tests/model_validation/sklearn/PrecisionRecallCurve.py +6 -1
- validmind/tests/model_validation/sklearn/ROCCurve.py +3 -1
- validmind/tests/model_validation/sklearn/RegressionErrors.py +6 -2
- validmind/tests/model_validation/sklearn/RegressionPerformance.py +13 -8
- validmind/tests/model_validation/sklearn/RegressionR2Square.py +8 -5
- validmind/tests/model_validation/sklearn/RobustnessDiagnosis.py +5 -1
- validmind/tests/model_validation/sklearn/SHAPGlobalImportance.py +34 -26
- validmind/tests/model_validation/sklearn/ScoreProbabilityAlignment.py +10 -2
- validmind/tests/model_validation/sklearn/SilhouettePlot.py +5 -1
- validmind/tests/model_validation/sklearn/VMeasure.py +12 -9
- validmind/tests/model_validation/sklearn/WeakspotsDiagnosis.py +15 -10
- validmind/tests/model_validation/statsmodels/CumulativePredictionProbabilities.py +5 -1
- validmind/tests/model_validation/statsmodels/DurbinWatsonTest.py +6 -1
- validmind/tests/model_validation/statsmodels/GINITable.py +8 -1
- validmind/tests/model_validation/statsmodels/KolmogorovSmirnov.py +2 -2
- validmind/tests/model_validation/statsmodels/PredictionProbabilitiesHistogram.py +6 -2
- validmind/tests/model_validation/statsmodels/RegressionCoeffs.py +8 -2
- validmind/tests/model_validation/statsmodels/RegressionFeatureSignificance.py +3 -1
- validmind/tests/model_validation/statsmodels/RegressionModelForecastPlot.py +7 -2
- validmind/tests/model_validation/statsmodels/RegressionModelForecastPlotLevels.py +2 -0
- validmind/tests/model_validation/statsmodels/RegressionModelSensitivityPlot.py +2 -0
- validmind/tests/model_validation/statsmodels/RegressionModelSummary.py +4 -2
- validmind/tests/model_validation/statsmodels/RegressionPermutationFeatureImportance.py +3 -1
- validmind/tests/ongoing_monitoring/CalibrationCurveDrift.py +11 -1
- validmind/tests/ongoing_monitoring/ClassificationAccuracyDrift.py +10 -2
- validmind/tests/ongoing_monitoring/ConfusionMatrixDrift.py +8 -1
- validmind/tests/ongoing_monitoring/CumulativePredictionProbabilitiesDrift.py +18 -2
- validmind/tests/ongoing_monitoring/FeatureDrift.py +9 -2
- validmind/tests/ongoing_monitoring/PredictionAcrossEachFeature.py +8 -2
- validmind/tests/ongoing_monitoring/PredictionCorrelation.py +13 -2
- validmind/tests/ongoing_monitoring/PredictionProbabilitiesHistogramDrift.py +13 -2
- validmind/tests/ongoing_monitoring/ROCCurveDrift.py +16 -2
- validmind/tests/ongoing_monitoring/ScoreBandsDrift.py +11 -2
- validmind/tests/ongoing_monitoring/TargetPredictionDistributionPlot.py +13 -2
- validmind/tests/output.py +66 -11
- validmind/tests/prompt_validation/Clarity.py +1 -1
- validmind/tests/prompt_validation/NegativeInstruction.py +1 -1
- validmind/tests/prompt_validation/Robustness.py +6 -1
- validmind/tests/prompt_validation/Specificity.py +1 -1
- validmind/tests/run.py +28 -14
- validmind/tests/test_providers.py +28 -35
- validmind/tests/utils.py +17 -4
- validmind/unit_metrics/__init__.py +1 -1
- validmind/utils.py +295 -31
- validmind/vm_models/dataset/dataset.py +19 -16
- validmind/vm_models/dataset/utils.py +5 -3
- validmind/vm_models/figure.py +6 -6
- validmind/vm_models/input.py +6 -5
- validmind/vm_models/model.py +5 -5
- validmind/vm_models/result/result.py +122 -43
- validmind/vm_models/result/utils.py +9 -28
- validmind/vm_models/test_suite/__init__.py +5 -0
- validmind/vm_models/test_suite/runner.py +5 -5
- validmind/vm_models/test_suite/summary.py +20 -2
- validmind/vm_models/test_suite/test.py +6 -6
- validmind/vm_models/test_suite/test_suite.py +10 -10
- {validmind-2.8.10.dist-info → validmind-2.8.20.dist-info}/METADATA +4 -5
- {validmind-2.8.10.dist-info → validmind-2.8.20.dist-info}/RECORD +189 -188
- {validmind-2.8.10.dist-info → validmind-2.8.20.dist-info}/WHEEL +1 -1
- {validmind-2.8.10.dist-info → validmind-2.8.20.dist-info}/LICENSE +0 -0
- {validmind-2.8.10.dist-info → validmind-2.8.20.dist-info}/entry_points.txt +0 -0
validmind/errors.py
CHANGED
@@ -15,6 +15,8 @@ from typing import Optional
|
|
15
15
|
|
16
16
|
|
17
17
|
class BaseError(Exception):
|
18
|
+
"""Common base class for all non-exit exceptions."""
|
19
|
+
|
18
20
|
def __init__(self, message=""):
|
19
21
|
self.message = message
|
20
22
|
super().__init__(self.message)
|
@@ -52,7 +54,7 @@ class MissingCacheResultsArgumentsError(BaseError):
|
|
52
54
|
|
53
55
|
class MissingOrInvalidModelPredictFnError(BaseError):
|
54
56
|
"""
|
55
|
-
When the
|
57
|
+
When the PyTorch model is missing a predict function or its predict
|
56
58
|
method does not have the expected arguments.
|
57
59
|
"""
|
58
60
|
|
@@ -71,7 +73,7 @@ class InvalidAPICredentialsError(APIRequestError):
|
|
71
73
|
def description(self, *args, **kwargs):
|
72
74
|
return (
|
73
75
|
self.message
|
74
|
-
or "Invalid API credentials. Please ensure that you have provided the correct values for
|
76
|
+
or "Invalid API credentials. Please ensure that you have provided the correct values for API_KEY and API_SECRET."
|
75
77
|
)
|
76
78
|
|
77
79
|
|
@@ -115,7 +117,7 @@ class InvalidTestResultsError(APIRequestError):
|
|
115
117
|
|
116
118
|
class InvalidTestParametersError(BaseError):
|
117
119
|
"""
|
118
|
-
When
|
120
|
+
When invalid parameters are provided for the test.
|
119
121
|
"""
|
120
122
|
|
121
123
|
pass
|
@@ -123,7 +125,15 @@ class InvalidTestParametersError(BaseError):
|
|
123
125
|
|
124
126
|
class InvalidInputError(BaseError):
|
125
127
|
"""
|
126
|
-
When an invalid input object.
|
128
|
+
When an invalid input object is provided.
|
129
|
+
"""
|
130
|
+
|
131
|
+
pass
|
132
|
+
|
133
|
+
|
134
|
+
class InvalidParameterError(BaseError):
|
135
|
+
"""
|
136
|
+
When an invalid parameter is provided.
|
127
137
|
"""
|
128
138
|
|
129
139
|
pass
|
@@ -131,7 +141,7 @@ class InvalidInputError(BaseError):
|
|
131
141
|
|
132
142
|
class InvalidTextObjectError(APIRequestError):
|
133
143
|
"""
|
134
|
-
When an invalid
|
144
|
+
When an invalid Metadata (Text) object is sent to the API.
|
135
145
|
"""
|
136
146
|
|
137
147
|
pass
|
@@ -155,7 +165,7 @@ class InvalidXGBoostTrainedModelError(BaseError):
|
|
155
165
|
|
156
166
|
class LoadTestError(BaseError):
|
157
167
|
"""
|
158
|
-
Exception raised when an error occurs while loading a test
|
168
|
+
Exception raised when an error occurs while loading a test.
|
159
169
|
"""
|
160
170
|
|
161
171
|
def __init__(self, message: str, original_error: Optional[Exception] = None):
|
@@ -323,7 +333,7 @@ class SkipTestError(BaseError):
|
|
323
333
|
def raise_api_error(error_string):
|
324
334
|
"""
|
325
335
|
Safely try to parse JSON from the response message in case the API
|
326
|
-
returns a non-JSON string or if the API returns a non-standard error
|
336
|
+
returns a non-JSON string or if the API returns a non-standard error.
|
327
337
|
"""
|
328
338
|
try:
|
329
339
|
json_response = json.loads(error_string)
|
validmind/input_registry.py
CHANGED
validmind/logging.py
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
# See the LICENSE file in the root of this repository for details.
|
3
3
|
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
4
|
|
5
|
-
"""ValidMind logging module
|
5
|
+
"""ValidMind logging module"""
|
6
6
|
|
7
7
|
import logging
|
8
8
|
import os
|
9
9
|
import time
|
10
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, TypeVar
|
10
11
|
|
11
12
|
import sentry_sdk
|
12
13
|
from sentry_sdk.utils import event_from_exception, exc_info_from_error
|
@@ -16,8 +17,8 @@ from .__version__ import __version__
|
|
16
17
|
__dsn = "https://48f446843657444aa1e2c0d716ef864b@o1241367.ingest.sentry.io/4505239625465856"
|
17
18
|
|
18
19
|
|
19
|
-
def _get_log_level():
|
20
|
-
"""Get the log level from the environment variable"""
|
20
|
+
def _get_log_level() -> int:
|
21
|
+
"""Get the log level from the environment variable."""
|
21
22
|
log_level_str = os.getenv("LOG_LEVEL", "INFO").upper()
|
22
23
|
|
23
24
|
if log_level_str not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
|
@@ -26,8 +27,10 @@ def _get_log_level():
|
|
26
27
|
return logging.getLevelName(log_level_str)
|
27
28
|
|
28
29
|
|
29
|
-
def get_logger(
|
30
|
-
|
30
|
+
def get_logger(
|
31
|
+
name: str = "validmind", log_level: Optional[int] = None
|
32
|
+
) -> logging.Logger:
|
33
|
+
"""Get a logger for the given module name."""
|
31
34
|
formatter = logging.Formatter(
|
32
35
|
fmt="%(asctime)s - %(levelname)s(%(name)s): %(message)s"
|
33
36
|
)
|
@@ -52,18 +55,21 @@ def get_logger(name="validmind", log_level=None):
|
|
52
55
|
return logger
|
53
56
|
|
54
57
|
|
55
|
-
def init_sentry(server_config):
|
56
|
-
"""Initialize Sentry SDK for sending logs back to ValidMind
|
58
|
+
def init_sentry(server_config: Dict[str, Any]) -> None:
|
59
|
+
"""Initialize Sentry SDK for sending logs back to ValidMind.
|
57
60
|
|
58
|
-
This will usually only be called by the
|
59
|
-
|
61
|
+
This will usually only be called by the API client module to initialize the
|
62
|
+
Sentry connection after the user calls `validmind.init()`. This is because the DSN
|
60
63
|
and other config options will be returned by the API.
|
61
64
|
|
62
65
|
Args:
|
63
|
-
|
64
|
-
- send_logs (bool): Whether to send logs to Sentry (gets removed)
|
65
|
-
- dsn (str): The Sentry DSN
|
66
|
-
...: Other config options for Sentry
|
66
|
+
server_config (Dict[str, Any]): The config dictionary returned by the API.
|
67
|
+
- send_logs (bool): Whether to send logs to Sentry (gets removed).
|
68
|
+
- dsn (str): The Sentry DSN.
|
69
|
+
...: Other config options for Sentry.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
None.
|
67
73
|
"""
|
68
74
|
if os.getenv("VM_NO_TELEMETRY", False):
|
69
75
|
return
|
@@ -88,19 +94,27 @@ def init_sentry(server_config):
|
|
88
94
|
logger.debug(f"Sentry error: {str(e)}")
|
89
95
|
|
90
96
|
|
91
|
-
|
92
|
-
|
97
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
98
|
+
AF = TypeVar("AF", bound=Callable[..., Awaitable[Any]])
|
99
|
+
|
100
|
+
|
101
|
+
def log_performance(
|
102
|
+
name: Optional[str] = None,
|
103
|
+
logger: Optional[logging.Logger] = None,
|
104
|
+
force: bool = False,
|
105
|
+
) -> Callable[[F], F]:
|
106
|
+
"""Decorator to log the time it takes to run a function.
|
93
107
|
|
94
108
|
Args:
|
95
109
|
name (str, optional): The name of the function. Defaults to None.
|
96
110
|
logger (logging.Logger, optional): The logger to use. Defaults to None.
|
97
|
-
force (bool, optional): Whether to force logging even if env var is off
|
111
|
+
force (bool, optional): Whether to force logging even if env var is off.
|
98
112
|
|
99
113
|
Returns:
|
100
|
-
|
114
|
+
Callable: The decorated function.
|
101
115
|
"""
|
102
116
|
|
103
|
-
def decorator(func):
|
117
|
+
def decorator(func: F) -> F:
|
104
118
|
# check if log level is set to debug
|
105
119
|
if _get_log_level() != logging.DEBUG and not force:
|
106
120
|
return func
|
@@ -113,7 +127,7 @@ def log_performance(name=None, logger=None, force=False):
|
|
113
127
|
if name is None:
|
114
128
|
name = func.__name__
|
115
129
|
|
116
|
-
def wrapped(*args, **kwargs):
|
130
|
+
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
117
131
|
time1 = time.perf_counter()
|
118
132
|
return_val = func(*args, **kwargs)
|
119
133
|
time2 = time.perf_counter()
|
@@ -127,18 +141,13 @@ def log_performance(name=None, logger=None, force=False):
|
|
127
141
|
return decorator
|
128
142
|
|
129
143
|
|
130
|
-
async def log_performance_async(
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
force (bool, optional): Whether to force logging even if env var is off
|
138
|
-
|
139
|
-
Returns:
|
140
|
-
function: The decorated function
|
141
|
-
"""
|
144
|
+
async def log_performance_async(
|
145
|
+
func: AF,
|
146
|
+
name: Optional[str] = None,
|
147
|
+
logger: Optional[logging.Logger] = None,
|
148
|
+
force: bool = False,
|
149
|
+
) -> AF:
|
150
|
+
"""Async version of log_performance decorator"""
|
142
151
|
# check if log level is set to debug
|
143
152
|
if _get_log_level() != logging.DEBUG and not force:
|
144
153
|
return func
|
@@ -149,7 +158,7 @@ async def log_performance_async(func, name=None, logger=None, force=False):
|
|
149
158
|
if name is None:
|
150
159
|
name = func.__name__
|
151
160
|
|
152
|
-
async def wrap(*args, **kwargs):
|
161
|
+
async def wrap(*args: Any, **kwargs: Any) -> Any:
|
153
162
|
time1 = time.perf_counter()
|
154
163
|
return_val = await func(*args, **kwargs)
|
155
164
|
time2 = time.perf_counter()
|
@@ -161,11 +170,11 @@ async def log_performance_async(func, name=None, logger=None, force=False):
|
|
161
170
|
return wrap
|
162
171
|
|
163
172
|
|
164
|
-
def send_single_error(error: Exception):
|
165
|
-
"""Send a single error to Sentry
|
173
|
+
def send_single_error(error: Exception) -> None:
|
174
|
+
"""Send a single error to Sentry.
|
166
175
|
|
167
176
|
Args:
|
168
|
-
error (Exception): The exception to send
|
177
|
+
error (Exception): The exception to send.
|
169
178
|
"""
|
170
179
|
event, hint = event_from_exception(exc_info_from_error(error))
|
171
180
|
client = sentry_sdk.Client(__dsn, release=f"validmind-python@{__version__}")
|
validmind/models/foundation.py
CHANGED
@@ -26,9 +26,9 @@ class FoundationModel(FunctionModel):
|
|
26
26
|
|
27
27
|
Attributes:
|
28
28
|
predict_fn (callable): The predict function that should take a prompt as input
|
29
|
-
|
29
|
+
and return the result from the model
|
30
30
|
prompt (Prompt): The prompt object that defines the prompt template and the
|
31
|
-
|
31
|
+
variables (if any)
|
32
32
|
name (str, optional): The name of the model. Defaults to name of the predict_fn
|
33
33
|
"""
|
34
34
|
|
validmind/models/function.py
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# See the LICENSE file in the root of this repository for details.
|
3
3
|
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
4
|
|
5
|
+
from typing import Any, Dict, List
|
6
|
+
|
5
7
|
from validmind.vm_models.model import VMModel
|
6
8
|
|
7
9
|
|
@@ -18,7 +20,12 @@ class Input(dict):
|
|
18
20
|
def __delitem__(self, _):
|
19
21
|
raise TypeError("Cannot delete keys from Input")
|
20
22
|
|
21
|
-
def get_new(self):
|
23
|
+
def get_new(self) -> Dict[str, Any]:
|
24
|
+
"""Get the newly added key-value pairs.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Dict[str, Any]: Dictionary containing only the newly added key-value pairs.
|
28
|
+
"""
|
22
29
|
return {k: self[k] for k in self._new}
|
23
30
|
|
24
31
|
|
@@ -41,13 +48,13 @@ class FunctionModel(VMModel):
|
|
41
48
|
|
42
49
|
self.name = self.name or self.predict_fn.__name__
|
43
50
|
|
44
|
-
def predict(self, X):
|
51
|
+
def predict(self, X) -> List[Any]:
|
45
52
|
"""Compute predictions for the input (X)
|
46
53
|
|
47
54
|
Args:
|
48
55
|
X (pandas.DataFrame): The input features to predict on
|
49
56
|
|
50
57
|
Returns:
|
51
|
-
|
58
|
+
List[Any]: The predictions
|
52
59
|
"""
|
53
60
|
return [self.predict_fn(x) for x in X.to_dict(orient="records")]
|
validmind/template.py
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
# See the LICENSE file in the root of this repository for details.
|
3
3
|
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
4
|
|
5
|
-
from
|
5
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
6
|
+
|
7
|
+
from ipywidgets import HTML, Accordion, VBox, Widget
|
6
8
|
|
7
9
|
from .html_templates.content_blocks import (
|
8
10
|
failed_content_block_html,
|
@@ -29,8 +31,10 @@ CONTENT_TYPE_MAP = {
|
|
29
31
|
|
30
32
|
|
31
33
|
def _convert_sections_to_section_tree(
|
32
|
-
sections,
|
33
|
-
|
34
|
+
sections: List[Dict[str, Any]],
|
35
|
+
parent_id: str = "_root_",
|
36
|
+
start_section_id: Optional[str] = None,
|
37
|
+
) -> List[Dict[str, Any]]:
|
34
38
|
section_tree = []
|
35
39
|
|
36
40
|
for section in sections:
|
@@ -53,7 +57,7 @@ def _convert_sections_to_section_tree(
|
|
53
57
|
return sorted(section_tree, key=lambda x: x.get("order", 0))
|
54
58
|
|
55
59
|
|
56
|
-
def _create_content_widget(content):
|
60
|
+
def _create_content_widget(content: Dict[str, Any]) -> Widget:
|
57
61
|
content_type = CONTENT_TYPE_MAP[content["content_type"]]
|
58
62
|
|
59
63
|
if content["content_type"] not in ["metric", "test"]:
|
@@ -75,7 +79,9 @@ def _create_content_widget(content):
|
|
75
79
|
)
|
76
80
|
|
77
81
|
|
78
|
-
def _create_sub_section_widget(
|
82
|
+
def _create_sub_section_widget(
|
83
|
+
sub_sections: List[Dict[str, Any]], section_number: str
|
84
|
+
) -> Union[HTML, Accordion]:
|
79
85
|
if not sub_sections:
|
80
86
|
return HTML("<p>Empty Section</p>")
|
81
87
|
|
@@ -111,7 +117,7 @@ def _create_sub_section_widget(sub_sections, section_number):
|
|
111
117
|
return accordion
|
112
118
|
|
113
119
|
|
114
|
-
def _create_section_widget(tree):
|
120
|
+
def _create_section_widget(tree: List[Dict[str, Any]]) -> Accordion:
|
115
121
|
widget = Accordion()
|
116
122
|
for i, section in enumerate(tree):
|
117
123
|
sub_widget = None
|
@@ -139,11 +145,11 @@ def _create_section_widget(tree):
|
|
139
145
|
return widget
|
140
146
|
|
141
147
|
|
142
|
-
def preview_template(template):
|
143
|
-
"""Preview a template in Jupyter Notebook
|
148
|
+
def preview_template(template: str) -> None:
|
149
|
+
"""Preview a template in Jupyter Notebook.
|
144
150
|
|
145
151
|
Args:
|
146
|
-
template (dict): The template to preview
|
152
|
+
template (dict): The template to preview.
|
147
153
|
"""
|
148
154
|
if not is_notebook():
|
149
155
|
logger.warning("preview_template() only works in Jupyter Notebook")
|
@@ -154,7 +160,7 @@ def preview_template(template):
|
|
154
160
|
)
|
155
161
|
|
156
162
|
|
157
|
-
def _get_section_tests(section):
|
163
|
+
def _get_section_tests(section: Dict[str, Any]) -> List[str]:
|
158
164
|
"""
|
159
165
|
Get all the tests in a section and its subsections.
|
160
166
|
|
@@ -179,15 +185,15 @@ def _get_section_tests(section):
|
|
179
185
|
return tests
|
180
186
|
|
181
187
|
|
182
|
-
def _create_test_suite_section(section):
|
188
|
+
def _create_test_suite_section(section: Dict[str, Any]) -> Dict[str, Any]:
|
183
189
|
"""Create a section object for a test suite that contains the tests in a section
|
184
|
-
in the template
|
190
|
+
in the template.
|
185
191
|
|
186
192
|
Args:
|
187
|
-
section:
|
193
|
+
section: A section of a template (in tree form).
|
188
194
|
|
189
195
|
Returns:
|
190
|
-
A TestSuite section dict
|
196
|
+
A TestSuite section dict.
|
191
197
|
"""
|
192
198
|
if section_tests := _get_section_tests(section):
|
193
199
|
return {
|
@@ -197,16 +203,18 @@ def _create_test_suite_section(section):
|
|
197
203
|
}
|
198
204
|
|
199
205
|
|
200
|
-
def _create_template_test_suite(
|
206
|
+
def _create_template_test_suite(
|
207
|
+
template: str, section: Optional[str] = None
|
208
|
+
) -> Type[TestSuite]:
|
201
209
|
"""
|
202
210
|
Create and run a test suite from a template.
|
203
211
|
|
204
212
|
Args:
|
205
|
-
template: A valid flat template
|
206
|
-
section: The section of the template to run
|
213
|
+
template: A valid flat template.
|
214
|
+
section: The section of the template to run. Runs all sections if not provided.
|
207
215
|
|
208
216
|
Returns:
|
209
|
-
A dynamically-
|
217
|
+
A dynamically-created TestSuite Class.
|
210
218
|
"""
|
211
219
|
section_tree = _convert_sections_to_section_tree(
|
212
220
|
sections=template["sections"],
|
@@ -229,17 +237,17 @@ def _create_template_test_suite(template, section=None):
|
|
229
237
|
)
|
230
238
|
|
231
239
|
|
232
|
-
def get_template_test_suite(template, section=None):
|
233
|
-
"""Get a TestSuite instance containing all tests in a template
|
240
|
+
def get_template_test_suite(template: str, section: Optional[str] = None) -> TestSuite:
|
241
|
+
"""Get a TestSuite instance containing all tests in a template.
|
234
242
|
|
235
243
|
This function will collect all tests used in a template into a dynamically-created
|
236
|
-
TestSuite object
|
244
|
+
TestSuite object.
|
237
245
|
|
238
246
|
Args:
|
239
247
|
template: A valid flat template
|
240
248
|
section: The section of the template to run (if not provided, run all sections)
|
241
249
|
|
242
250
|
Returns:
|
243
|
-
The TestSuite instance
|
251
|
+
The TestSuite instance.
|
244
252
|
"""
|
245
253
|
return _create_template_test_suite(template, section)()
|
@@ -141,7 +141,7 @@ def list_suites(pretty: bool = True):
|
|
141
141
|
return format_dataframe(pd.DataFrame(table))
|
142
142
|
|
143
143
|
|
144
|
-
def describe_suite(test_suite_id: str, verbose=False):
|
144
|
+
def describe_suite(test_suite_id: str, verbose: bool = False) -> pd.DataFrame:
|
145
145
|
"""
|
146
146
|
Describes a Test Suite by ID
|
147
147
|
|
@@ -150,7 +150,7 @@ def describe_suite(test_suite_id: str, verbose=False):
|
|
150
150
|
verbose: If True, describe all plans and tests in the Test Suite
|
151
151
|
|
152
152
|
Returns:
|
153
|
-
|
153
|
+
pd.DataFrame: A formatted table with the Test Suite description
|
154
154
|
"""
|
155
155
|
test_suite = get_by_id(test_suite_id)
|
156
156
|
|
validmind/tests/_store.py
CHANGED
@@ -5,6 +5,8 @@
|
|
5
5
|
"""Module for storing loaded tests and test providers"""
|
6
6
|
|
7
7
|
|
8
|
+
from typing import Any, Callable, Optional
|
9
|
+
|
8
10
|
from .test_providers import TestProvider, ValidMindTestProvider
|
9
11
|
|
10
12
|
|
@@ -65,19 +67,26 @@ class TestStore:
|
|
65
67
|
def __init__(self):
|
66
68
|
self.tests = {}
|
67
69
|
|
68
|
-
def get_test(self, test_id: str):
|
70
|
+
def get_test(self, test_id: str) -> Optional[Callable[..., Any]]:
|
69
71
|
"""Get a test by test ID
|
70
72
|
|
71
73
|
Args:
|
72
74
|
test_id (str): The test ID
|
73
75
|
|
74
76
|
Returns:
|
75
|
-
|
77
|
+
Optional[Callable[..., Any]]: The test function if found, None otherwise
|
76
78
|
"""
|
77
79
|
return self.tests.get(test_id)
|
78
80
|
|
79
|
-
def register_test(
|
80
|
-
|
81
|
+
def register_test(
|
82
|
+
self, test_id: str, test: Optional[Callable[..., Any]] = None
|
83
|
+
) -> None:
|
84
|
+
"""Register a test
|
85
|
+
|
86
|
+
Args:
|
87
|
+
test_id (str): The test ID
|
88
|
+
test (Optional[Callable[..., Any]], optional): The test function. Defaults to None.
|
89
|
+
"""
|
81
90
|
self.tests[test_id] = test
|
82
91
|
|
83
92
|
|
validmind/tests/comparison.py
CHANGED
@@ -146,7 +146,9 @@ def _combine_tables(results: List[TestResult]) -> List[pd.DataFrame]:
|
|
146
146
|
return [_combine_single_table(results, i) for i in range(len(results[0].tables))]
|
147
147
|
|
148
148
|
|
149
|
-
def _build_input_param_string(
|
149
|
+
def _build_input_param_string(
|
150
|
+
result: TestResult, results: List[TestResult], show_params: bool
|
151
|
+
) -> str:
|
150
152
|
"""Build a string repr of unique inputs + params for a figure title"""
|
151
153
|
parts = []
|
152
154
|
unique_inputs = _get_unique_inputs(results)
|
@@ -162,19 +164,29 @@ def _build_input_param_string(result: TestResult, results: List[TestResult]) ->
|
|
162
164
|
input_val = _get_input_key(input_obj)
|
163
165
|
parts.append(f"{input_name}={input_val}")
|
164
166
|
|
165
|
-
#
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
167
|
+
# Handle params if show_params is enabled
|
168
|
+
if show_params and result.params:
|
169
|
+
unique_params = _get_unique_params(results)
|
170
|
+
# If there's only one unique value for a param, don't show it
|
171
|
+
# unless there is only one unique value for all params and no inputs shown
|
172
|
+
should_show = (
|
173
|
+
all(len(unique_params[param_name]) == 1 for param_name in unique_params)
|
174
|
+
and not parts
|
175
|
+
)
|
176
|
+
for param_name, param_value in result.params.items():
|
177
|
+
if should_show or len(unique_params[param_name]) > 1:
|
178
|
+
# Convert the param_value to a string representation
|
179
|
+
if isinstance(param_value, list):
|
180
|
+
# For lists, join elements with commas
|
181
|
+
str_value = ",".join(str(v) for v in param_value)
|
182
|
+
elif hasattr(param_value, "__str__"):
|
183
|
+
# Use string representation if available
|
184
|
+
str_value = str(param_value)
|
185
|
+
else:
|
186
|
+
# Default fallback
|
187
|
+
str_value = repr(param_value)
|
188
|
+
|
189
|
+
parts.append(f"{param_name}={str_value}")
|
178
190
|
|
179
191
|
return ", ".join(parts)
|
180
192
|
|
@@ -207,7 +219,7 @@ def _update_figure_title(figure: Any, input_param_str: str) -> None:
|
|
207
219
|
raise ValueError(f"Unsupported figure type: {type(figure)}")
|
208
220
|
|
209
221
|
|
210
|
-
def _combine_figures(results: List[TestResult]) -> List[Any]:
|
222
|
+
def _combine_figures(results: List[TestResult], show_params: bool) -> List[Any]:
|
211
223
|
"""Combine figures from multiple test results (gets raw figure objects, not vm Figures)"""
|
212
224
|
combined_figures = []
|
213
225
|
|
@@ -216,7 +228,7 @@ def _combine_figures(results: List[TestResult]) -> List[Any]:
|
|
216
228
|
# update the figure object in-place with the new title
|
217
229
|
_update_figure_title(
|
218
230
|
figure=figure.figure,
|
219
|
-
input_param_str=_build_input_param_string(result, results),
|
231
|
+
input_param_str=_build_input_param_string(result, results, show_params),
|
220
232
|
)
|
221
233
|
combined_figures.append(figure)
|
222
234
|
|
@@ -279,35 +291,53 @@ def get_comparison_test_configs(
|
|
279
291
|
A list of test configurations.
|
280
292
|
"""
|
281
293
|
|
282
|
-
# Convert list of dicts to dict of lists if necessary
|
294
|
+
# Convert list of dicts to dict of lists if necessary for input_grid
|
283
295
|
def list_to_dict(grid_list):
|
284
296
|
return {k: [d[k] for d in grid_list] for k in grid_list[0].keys()}
|
285
297
|
|
298
|
+
# Handle input_grid the same way as before
|
286
299
|
if isinstance(input_grid, list):
|
287
300
|
input_grid = list_to_dict(input_grid)
|
288
301
|
|
289
|
-
if isinstance(param_grid, list):
|
290
|
-
param_grid = list_to_dict(param_grid)
|
291
|
-
|
292
302
|
test_configs = []
|
293
303
|
|
294
|
-
if
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
304
|
+
# Check if param_grid is a list of dictionaries
|
305
|
+
is_param_grid_list = isinstance(param_grid, list)
|
306
|
+
|
307
|
+
# Special handling for list-based param_grid
|
308
|
+
if is_param_grid_list:
|
309
|
+
if input_grid:
|
310
|
+
# Generate all combinations of input_grid and each param dictionary
|
311
|
+
input_combinations = _cartesian_product(input_grid)
|
312
|
+
test_configs = [
|
313
|
+
{"inputs": i, "params": p}
|
314
|
+
for i in input_combinations
|
315
|
+
for p in param_grid
|
316
|
+
]
|
317
|
+
else:
|
318
|
+
# Each dictionary in param_grid is a specific test configuration
|
319
|
+
test_configs = [{"inputs": inputs or {}, "params": p} for p in param_grid]
|
320
|
+
|
321
|
+
# Dictionary-based param_grid
|
322
|
+
elif param_grid:
|
323
|
+
if input_grid:
|
324
|
+
input_combinations = _cartesian_product(input_grid)
|
325
|
+
param_combinations = _cartesian_product(param_grid)
|
326
|
+
test_configs = [
|
327
|
+
{"inputs": i, "params": p}
|
328
|
+
for i, p in product(input_combinations, param_combinations)
|
329
|
+
]
|
330
|
+
else:
|
331
|
+
param_combinations = _cartesian_product(param_grid)
|
332
|
+
test_configs = [
|
333
|
+
{"inputs": inputs or {}, "params": p} for p in param_combinations
|
334
|
+
]
|
335
|
+
# Just input_grid, no param_grid
|
301
336
|
elif input_grid:
|
302
337
|
input_combinations = _cartesian_product(input_grid)
|
303
338
|
test_configs = [
|
304
339
|
{"inputs": i, "params": params or {}} for i in input_combinations
|
305
340
|
]
|
306
|
-
elif param_grid:
|
307
|
-
param_combinations = _cartesian_product(param_grid)
|
308
|
-
test_configs = [
|
309
|
-
{"inputs": inputs or {}, "params": p} for p in param_combinations
|
310
|
-
]
|
311
341
|
|
312
342
|
return test_configs
|
313
343
|
|
@@ -333,12 +363,14 @@ def _combine_raw_data(results: List[TestResult]) -> RawData:
|
|
333
363
|
|
334
364
|
def combine_results(
|
335
365
|
results: List[TestResult],
|
366
|
+
show_params: bool,
|
336
367
|
) -> Tuple[List[Any], Dict[str, List[Any]], Dict[str, List[Any]]]:
|
337
368
|
"""
|
338
369
|
Combine multiple test results into a single set of outputs.
|
339
370
|
|
340
371
|
Args:
|
341
372
|
results: A list of TestResult objects to combine.
|
373
|
+
show_params: Whether to show parameter values in figure titles.
|
342
374
|
|
343
375
|
Returns:
|
344
376
|
A tuple containing:
|
@@ -353,7 +385,7 @@ def combine_results(
|
|
353
385
|
# handle tables (if any)
|
354
386
|
combined_outputs.extend(_combine_tables(results))
|
355
387
|
# handle figures (if any)
|
356
|
-
combined_outputs.extend(_combine_figures(results))
|
388
|
+
combined_outputs.extend(_combine_figures(results, show_params))
|
357
389
|
# handle threshold tests (i.e. tests that have pass/fail bool status)
|
358
390
|
if results[0].passed is not None:
|
359
391
|
combined_outputs.append(all(result.passed for result in results))
|
@@ -94,4 +94,7 @@ def ACFandPACFPlot(dataset: VMDataset):
|
|
94
94
|
figures.append(pacf_fig)
|
95
95
|
pacf_store[col] = pacf_values
|
96
96
|
|
97
|
-
return (
|
97
|
+
return (
|
98
|
+
*figures,
|
99
|
+
RawData(acf_values=acf_store, pacf_values=pacf_store, dataset=dataset.input_id),
|
100
|
+
)
|