mloda 0.4.1__py3-none-any.whl → 0.4.3__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.
Files changed (87) hide show
  1. mloda/core/abstract_plugins/components/{feature_group_version.py → base_feature_group_version.py} +1 -1
  2. mloda/core/abstract_plugins/compute_framework.py +1 -1
  3. mloda/core/abstract_plugins/feature_group.py +3 -3
  4. mloda/core/api/feature_config/__init__.py +15 -0
  5. {mloda_plugins/config/feature → mloda/core/api/feature_config}/loader.py +19 -62
  6. {mloda_plugins/config/feature → mloda/core/api/feature_config}/models.py +2 -2
  7. {mloda_plugins/config/feature → mloda/core/api/feature_config}/parser.py +1 -1
  8. mloda/core/api/request.py +6 -1
  9. mloda/provider/__init__.py +2 -2
  10. mloda/provider/py.typed +0 -0
  11. mloda/steward/py.typed +0 -0
  12. mloda/user/__init__.py +10 -2
  13. mloda/user/py.typed +0 -0
  14. mloda-0.4.3.dist-info/METADATA +314 -0
  15. {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/RECORD +84 -81
  16. mloda_plugins/compute_framework/base_implementations/duckdb/duckdb_framework.py +1 -1
  17. mloda_plugins/compute_framework/base_implementations/iceberg/iceberg_framework.py +1 -1
  18. mloda_plugins/compute_framework/base_implementations/pandas/dataframe.py +1 -1
  19. mloda_plugins/compute_framework/base_implementations/polars/dataframe.py +1 -1
  20. mloda_plugins/compute_framework/base_implementations/pyarrow/table.py +1 -1
  21. mloda_plugins/compute_framework/base_implementations/python_dict/python_dict_framework.py +1 -1
  22. mloda_plugins/compute_framework/base_implementations/spark/spark_framework.py +1 -1
  23. mloda_plugins/feature_group/experimental/aggregated_feature_group/base.py +2 -2
  24. mloda_plugins/feature_group/experimental/aggregated_feature_group/pandas.py +1 -1
  25. mloda_plugins/feature_group/experimental/aggregated_feature_group/polars_lazy.py +1 -1
  26. mloda_plugins/feature_group/experimental/aggregated_feature_group/pyarrow.py +1 -1
  27. mloda_plugins/feature_group/experimental/clustering/base.py +2 -2
  28. mloda_plugins/feature_group/experimental/clustering/pandas.py +1 -1
  29. mloda_plugins/feature_group/experimental/data_quality/missing_value/base.py +5 -5
  30. mloda_plugins/feature_group/experimental/data_quality/missing_value/pandas.py +1 -1
  31. mloda_plugins/feature_group/experimental/data_quality/missing_value/pyarrow.py +1 -1
  32. mloda_plugins/feature_group/experimental/data_quality/missing_value/python_dict.py +1 -1
  33. mloda_plugins/feature_group/experimental/dimensionality_reduction/base.py +3 -3
  34. mloda_plugins/feature_group/experimental/dimensionality_reduction/pandas.py +1 -1
  35. mloda_plugins/feature_group/experimental/dynamic_feature_group_factory/dynamic_feature_group_factory.py +4 -4
  36. mloda_plugins/feature_group/experimental/forecasting/base.py +3 -3
  37. mloda_plugins/feature_group/experimental/forecasting/pandas.py +1 -1
  38. mloda_plugins/feature_group/experimental/geo_distance/base.py +3 -3
  39. mloda_plugins/feature_group/experimental/geo_distance/pandas.py +1 -1
  40. mloda_plugins/feature_group/experimental/llm/cli.py +1 -1
  41. mloda_plugins/feature_group/experimental/llm/cli_features/refactor_git_cached.py +4 -4
  42. mloda_plugins/feature_group/experimental/llm/installed_packages_feature_group.py +5 -5
  43. mloda_plugins/feature_group/experimental/llm/list_directory_feature_group.py +2 -2
  44. mloda_plugins/feature_group/experimental/llm/llm_api/claude.py +11 -11
  45. mloda_plugins/feature_group/experimental/llm/llm_api/gemini.py +10 -10
  46. mloda_plugins/feature_group/experimental/llm/llm_api/llm_base_request.py +2 -2
  47. mloda_plugins/feature_group/experimental/llm/llm_api/openai.py +11 -11
  48. mloda_plugins/feature_group/experimental/llm/llm_api/request_loop.py +2 -2
  49. mloda_plugins/feature_group/experimental/llm/llm_file_selector.py +9 -9
  50. mloda_plugins/feature_group/experimental/node_centrality/base.py +2 -2
  51. mloda_plugins/feature_group/experimental/node_centrality/pandas.py +1 -1
  52. mloda_plugins/feature_group/experimental/sklearn/encoding/base.py +8 -8
  53. mloda_plugins/feature_group/experimental/sklearn/encoding/pandas.py +1 -1
  54. mloda_plugins/feature_group/experimental/sklearn/pipeline/base.py +3 -3
  55. mloda_plugins/feature_group/experimental/sklearn/pipeline/pandas.py +1 -1
  56. mloda_plugins/feature_group/experimental/sklearn/scaling/base.py +2 -2
  57. mloda_plugins/feature_group/experimental/sklearn/scaling/pandas.py +1 -1
  58. mloda_plugins/feature_group/experimental/source_input_feature.py +3 -3
  59. mloda_plugins/feature_group/experimental/text_cleaning/base.py +2 -2
  60. mloda_plugins/feature_group/experimental/text_cleaning/pandas.py +1 -1
  61. mloda_plugins/feature_group/experimental/text_cleaning/python_dict.py +1 -1
  62. mloda_plugins/feature_group/experimental/time_window/base.py +3 -3
  63. mloda_plugins/feature_group/experimental/time_window/pandas.py +1 -1
  64. mloda_plugins/feature_group/experimental/time_window/pyarrow.py +1 -1
  65. mloda_plugins/feature_group/input_data/api_data/api_data.py +27 -27
  66. mloda_plugins/feature_group/input_data/read_context_files.py +3 -3
  67. mloda_plugins/feature_group/input_data/read_db.py +1 -1
  68. mloda_plugins/feature_group/input_data/read_db_feature.py +1 -1
  69. mloda_plugins/feature_group/input_data/read_dbs/sqlite.py +4 -4
  70. mloda_plugins/feature_group/input_data/read_file.py +1 -1
  71. mloda_plugins/feature_group/input_data/read_file_feature.py +1 -1
  72. mloda_plugins/feature_group/input_data/read_files/csv.py +4 -4
  73. mloda_plugins/feature_group/input_data/read_files/feather.py +4 -4
  74. mloda_plugins/feature_group/input_data/read_files/json.py +4 -4
  75. mloda_plugins/feature_group/input_data/read_files/orc.py +4 -4
  76. mloda_plugins/feature_group/input_data/read_files/parquet.py +4 -4
  77. mloda_plugins/feature_group/input_data/read_files/text_file_reader.py +4 -4
  78. mloda_plugins/py.typed +0 -0
  79. mloda/__init__.py +0 -17
  80. mloda-0.4.1.dist-info/METADATA +0 -384
  81. mloda_plugins/config/__init__.py +0 -1
  82. /mloda_plugins/config/feature/__init__.py → /mloda/core/py.typed +0 -0
  83. {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/WHEEL +0 -0
  84. {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/entry_points.txt +0 -0
  85. {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/licenses/LICENSE.TXT +0 -0
  86. {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/licenses/NOTICE.md +0 -0
  87. {mloda-0.4.1.dist-info → mloda-0.4.3.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ from typing import Any, Type
7
7
  from abc import ABC
8
8
 
9
9
 
10
- class FeatureGroupVersion(ABC):
10
+ class BaseFeatureGroupVersion(ABC):
11
11
  @classmethod
12
12
  def mloda_version(cls) -> str:
13
13
  """
@@ -383,7 +383,7 @@ Example:
383
383
  )
384
384
  }}
385
385
 
386
- mlodaAPI.run_all(
386
+ mlodamloda.run_all(
387
387
  features=[Feature.int32_of("{feature_name}")],
388
388
  links=links,
389
389
  ...
@@ -8,7 +8,7 @@ from mloda.core.abstract_plugins.components.data_access_collection import DataAc
8
8
  from mloda.core.abstract_plugins.components.data_types import DataType
9
9
 
10
10
  from mloda.core.abstract_plugins.components.domain import Domain
11
- from mloda.core.abstract_plugins.components.feature_group_version import FeatureGroupVersion
11
+ from mloda.core.abstract_plugins.components.base_feature_group_version import BaseFeatureGroupVersion
12
12
  from mloda.core.abstract_plugins.components.feature_name import FeatureName
13
13
  from mloda.core.abstract_plugins.components.input_data.api.api_input_data import ApiInputData
14
14
  from mloda.core.abstract_plugins.components.input_data.base_input_data import BaseInputData
@@ -68,9 +68,9 @@ class FeatureGroup(ABC):
68
68
  making it easier to detect changes, manage compatibility, and debug issues.
69
69
 
70
70
  If you need to change the version of the feature group, you can do so by subclassing
71
- FeatureGroupVersion and overriding the version method. This allows you to create a new version system.
71
+ BaseFeatureGroupVersion and overriding the version method. This allows you to create a new version system.
72
72
  """
73
- return FeatureGroupVersion.version(cls)
73
+ return BaseFeatureGroupVersion.version(cls)
74
74
 
75
75
  @classmethod
76
76
  def input_data(cls) -> Optional[BaseInputData]:
@@ -0,0 +1,15 @@
1
+ """Feature configuration loading from JSON.
2
+
3
+ This module provides utilities for loading feature configurations from JSON files.
4
+ """
5
+
6
+ from mloda.core.api.feature_config.loader import load_features_from_config
7
+ from mloda.core.api.feature_config.models import FeatureConfig, feature_config_schema
8
+ from mloda.core.api.feature_config.parser import parse_json
9
+
10
+ __all__ = [
11
+ "load_features_from_config",
12
+ "FeatureConfig",
13
+ "feature_config_schema",
14
+ "parse_json",
15
+ ]
@@ -6,10 +6,10 @@ to mloda Feature instances.
6
6
  """
7
7
 
8
8
  from typing import List, Union, Dict, Any
9
- from mloda import Feature
10
- from mloda import Options
11
- from mloda_plugins.config.feature.parser import parse_json
12
- from mloda_plugins.config.feature.models import FeatureConfig
9
+ from mloda.user import Feature
10
+ from mloda.user import Options
11
+ from mloda.core.api.feature_config.parser import parse_json
12
+ from mloda.core.api.feature_config.models import FeatureConfig
13
13
  from mloda_plugins.feature_group.experimental.default_options_key import DefaultOptionKeys
14
14
 
15
15
 
@@ -34,20 +34,18 @@ def process_nested_features(options: Dict[str, Any]) -> Dict[str, Any]:
34
34
  nested_options = value.get("options", {})
35
35
  processed_nested_options = process_nested_features(nested_options)
36
36
 
37
- # Handle nested mloda_sources (can also be a dict)
38
- mloda_sources = value.get("mloda_sources")
39
- if mloda_sources:
40
- if isinstance(mloda_sources, list):
37
+ # Handle nested in_features (can also be a dict)
38
+ in_features = value.get("in_features")
39
+ if in_features:
40
+ if isinstance(in_features, list):
41
41
  # For list, convert each to string (single sources) or keep as-is
42
- processed_nested_options["in_features"] = (
43
- mloda_sources if len(mloda_sources) > 1 else mloda_sources[0]
44
- )
45
- elif isinstance(mloda_sources, dict):
46
- # Recursively create Feature for mloda_sources
47
- in_features = process_nested_features({"in_features": mloda_sources})["in_features"]
42
+ processed_nested_options["in_features"] = in_features if len(in_features) > 1 else in_features[0]
43
+ elif isinstance(in_features, dict):
44
+ # Recursively create Feature for in_features
45
+ in_features = process_nested_features({"in_features": in_features})["in_features"]
48
46
  processed_nested_options["in_features"] = in_features
49
47
  else:
50
- processed_nested_options["in_features"] = mloda_sources
48
+ processed_nested_options["in_features"] = in_features
51
49
 
52
50
  # Create the Feature object
53
51
  processed[key] = Feature(name=feature_name, options=processed_nested_options)
@@ -63,10 +61,6 @@ def process_nested_features(options: Dict[str, Any]) -> Dict[str, Any]:
63
61
  def load_features_from_config(config_str: str, format: str = "json") -> List[Union[Feature, str]]:
64
62
  """Load features from a configuration string.
65
63
 
66
- Uses a two-pass strategy to support feature references:
67
- - Pass 1: Create all Feature objects and build a name registry
68
- - Pass 2: Resolve @feature_name references to actual Feature objects
69
-
70
64
  Args:
71
65
  config_str: Configuration string in the specified format
72
66
  format: Configuration format (currently only "json" is supported)
@@ -79,16 +73,11 @@ def load_features_from_config(config_str: str, format: str = "json") -> List[Uni
79
73
 
80
74
  config_items = parse_json(config_str)
81
75
 
82
- # Pass 1: Create all Feature objects and build registry
83
76
  features: List[Union[Feature, str]] = []
84
- feature_registry: Dict[str, Feature] = {}
85
77
 
86
78
  for item in config_items:
87
79
  if isinstance(item, str):
88
- # Create a Feature object for string entries so they can be referenced
89
- feature = Feature(name=item, options={})
90
80
  features.append(item)
91
- feature_registry[item] = feature
92
81
  elif isinstance(item, FeatureConfig):
93
82
  # Build feature name with column index suffix if present
94
83
  feature_name = item.name
@@ -99,60 +88,28 @@ def load_features_from_config(config_str: str, format: str = "json") -> List[Uni
99
88
  if item.group_options is not None or item.context_options is not None:
100
89
  # Use new Options architecture with group/context separation
101
90
  context = item.context_options or {}
102
- # Handle mloda_sources if present
103
- if item.mloda_sources:
91
+ # Handle in_features if present
92
+ if item.in_features:
104
93
  # Always convert to frozenset for consistency
105
- context[DefaultOptionKeys.in_features] = frozenset(item.mloda_sources)
94
+ context[DefaultOptionKeys.in_features] = frozenset(item.in_features)
106
95
  options = Options(group=item.group_options or {}, context=context)
107
96
  feature = Feature(name=feature_name, options=options)
108
97
  features.append(feature)
109
- feature_registry[feature_name] = feature
110
- # Check if mloda_sources exists and create Options accordingly
111
- elif item.mloda_sources:
98
+ # Check if in_features exists and create Options accordingly
99
+ elif item.in_features:
112
100
  # Process nested features in options before creating Feature
113
101
  processed_options = process_nested_features(item.options)
114
102
  # Always convert to frozenset for consistency (even single items)
115
- source_value = frozenset(item.mloda_sources)
103
+ source_value = frozenset(item.in_features)
116
104
  options = Options(group=processed_options, context={DefaultOptionKeys.in_features: source_value})
117
105
  feature = Feature(name=feature_name, options=options)
118
106
  features.append(feature)
119
- feature_registry[feature_name] = feature
120
107
  else:
121
108
  # Process nested features in options before creating Feature
122
109
  processed_options = process_nested_features(item.options)
123
110
  feature = Feature(name=feature_name, options=processed_options)
124
111
  features.append(feature)
125
- feature_registry[feature_name] = feature
126
112
  else:
127
113
  raise ValueError(f"Unexpected config item type: {type(item)}")
128
114
 
129
- # Pass 2: Resolve @feature_name references to Feature objects
130
- for feat in features:
131
- if isinstance(feat, Feature):
132
- mloda_source = feat.options.context.get(DefaultOptionKeys.in_features)
133
- if mloda_source:
134
- # Handle both single string and frozenset of strings
135
- if isinstance(mloda_source, str) and mloda_source.startswith("@"):
136
- # Single reference string
137
- referenced_name = mloda_source[1:]
138
- if referenced_name in feature_registry:
139
- feat.options.context[DefaultOptionKeys.in_features] = feature_registry[referenced_name]
140
- else:
141
- raise ValueError(f"Feature reference '@{referenced_name}' not found in configuration")
142
- elif isinstance(mloda_source, frozenset):
143
- # Frozenset of sources - resolve any @ references
144
- resolved_sources = []
145
- for source in mloda_source:
146
- if isinstance(source, str) and source.startswith("@"):
147
- referenced_name = source[1:]
148
- if referenced_name in feature_registry:
149
- resolved_sources.append(feature_registry[referenced_name])
150
- else:
151
- raise ValueError(f"Feature reference '@{referenced_name}' not found in configuration")
152
- else:
153
- resolved_sources.append(source)
154
- # Only replace if we actually resolved any references
155
- if any(isinstance(s, str) and s.startswith("@") for s in mloda_source):
156
- feat.options.context[DefaultOptionKeys.in_features] = frozenset(resolved_sources)
157
-
158
115
  return features
@@ -15,7 +15,7 @@ class FeatureConfig:
15
15
 
16
16
  name: str
17
17
  options: Dict[str, Any] = field(default_factory=dict)
18
- mloda_sources: Optional[List[str]] = None
18
+ in_features: Optional[List[str]] = None
19
19
  group_options: Optional[Dict[str, Any]] = None
20
20
  context_options: Optional[Dict[str, Any]] = None
21
21
  column_index: Optional[int] = None
@@ -37,7 +37,7 @@ def feature_config_schema() -> Dict[str, Any]:
37
37
  "properties": {
38
38
  "name": {"type": "string"},
39
39
  "options": {"type": "object", "default": {}},
40
- "mloda_sources": {"type": "array", "items": {"type": "string"}},
40
+ "in_features": {"type": "array", "items": {"type": "string"}},
41
41
  "group_options": {"type": "object"},
42
42
  "context_options": {"type": "object"},
43
43
  "column_index": {"type": "integer"},
@@ -6,7 +6,7 @@ This module provides parsers for configuration files in JSON format.
6
6
 
7
7
  import json
8
8
  from typing import List
9
- from mloda_plugins.config.feature.models import FeatureConfig, FeatureConfigItem
9
+ from mloda.core.api.feature_config.models import FeatureConfig, FeatureConfigItem
10
10
 
11
11
 
12
12
  def parse_json(config_str: str) -> List[FeatureConfigItem]:
mloda/core/api/request.py CHANGED
@@ -20,6 +20,11 @@ from mloda_plugins.feature_group.experimental.default_options_key import Default
20
20
 
21
21
 
22
22
  class mlodaAPI:
23
+ """Main API for executing mloda feature requests.
24
+
25
+ For JSON-based feature configuration, see `load_features_from_config()`.
26
+ """
27
+
23
28
  def __init__(
24
29
  self,
25
30
  requested_features: Union[Features, list[Union[Feature, str]]],
@@ -112,7 +117,7 @@ class mlodaAPI:
112
117
  List of computed results.
113
118
 
114
119
  Example:
115
- result = mlodaAPI.run_all(
120
+ result = mlodamloda.run_all(
116
121
  features,
117
122
  api_data={"UserQuery": {"row_index": [0], "query": ["hello"]}}
118
123
  )
@@ -2,7 +2,7 @@
2
2
  from mloda.core.abstract_plugins.feature_group import FeatureGroup as FeatureGroup
3
3
 
4
4
  # Versioning
5
- from mloda.core.abstract_plugins.components.feature_group_version import FeatureGroupVersion
5
+ from mloda.core.abstract_plugins.components.base_feature_group_version import BaseFeatureGroupVersion
6
6
  from mloda.core.abstract_plugins.compute_framework import ComputeFramework as ComputeFramework
7
7
 
8
8
  # Utilities
@@ -60,7 +60,7 @@ __all__ = [
60
60
  # Base classes
61
61
  "FeatureGroup",
62
62
  # Versioning
63
- "FeatureGroupVersion",
63
+ "BaseFeatureGroupVersion",
64
64
  "ComputeFramework",
65
65
  # Utilities
66
66
  "HashableDict",
File without changes
mloda/steward/py.typed ADDED
File without changes
mloda/user/__init__.py CHANGED
@@ -1,5 +1,7 @@
1
1
  # API
2
- from mloda.core.api.request import mlodaAPI as API
2
+ from mloda.core.api.request import mlodaAPI
3
+
4
+ mloda = mlodaAPI
3
5
 
4
6
  # Features
5
7
  from mloda.core.abstract_plugins.components.feature import Feature
@@ -28,9 +30,13 @@ from mloda.core.abstract_plugins.components.parallelization_modes import Paralle
28
30
  from mloda.core.abstract_plugins.plugin_loader.plugin_loader import PluginLoader
29
31
  from mloda.core.abstract_plugins.components.plugin_option.plugin_collector import PluginCollector
30
32
 
33
+ # Config loading
34
+ from mloda.core.api.feature_config.loader import load_features_from_config
35
+
31
36
  __all__ = [
32
37
  # API
33
- "API",
38
+ "mlodaAPI",
39
+ "mloda",
34
40
  # Features
35
41
  "Feature",
36
42
  "Features",
@@ -54,4 +60,6 @@ __all__ = [
54
60
  # Plugin discovery
55
61
  "PluginLoader",
56
62
  "PluginCollector",
63
+ # Config loading
64
+ "load_features_from_config",
57
65
  ]
mloda/user/py.typed ADDED
File without changes
@@ -0,0 +1,314 @@
1
+ Metadata-Version: 2.4
2
+ Name: mloda
3
+ Version: 0.4.3
4
+ Summary: mloda: One Data Access for ML and AI
5
+ Author-email: Tom Kaltofen <info@mloda.ai>
6
+ License: Apache-2.0
7
+ Project-URL: Bug Tracker, https://github.com/mloda-ai/mloda/issues
8
+ Project-URL: Documentation, https://mloda-ai.github.io/mloda/
9
+ Project-URL: Source Code, https://github.com/mloda-ai/mloda
10
+ Project-URL: PyPI, https://pypi.org/project/mloda/
11
+ Project-URL: Homepage, https://mloda.ai
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: <3.14,>=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE.TXT
16
+ License-File: NOTICE.md
17
+ Requires-Dist: pyarrow
18
+ Dynamic: license-file
19
+
20
+ # [mloda.ai](https://mloda.ai): Open Data Access for ML & AI
21
+
22
+ [![Website](https://img.shields.io/badge/website-mloda.ai-blue.svg)](https://mloda.ai)
23
+ [![Documentation](https://img.shields.io/badge/docs-github.io-blue.svg)](https://mloda-ai.github.io/mloda/)
24
+ [![PyPI version](https://badge.fury.io/py/mloda.svg)](https://badge.fury.io/py/mloda)
25
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/mloda-ai/mloda/blob/main/LICENSE.TXT)
26
+ [![Tests](https://img.shields.io/badge/tests-1500%2B-green.svg)](https://github.com/mloda-ai/mloda)
27
+
28
+ > **Declarative data access for AI agents. Describe what you need - mloda delivers it.**
29
+
30
+ ```bash
31
+ pip install mloda
32
+ ```
33
+
34
+ ## 30-Second Example
35
+
36
+ Your AI describes what it needs. mloda figures out how to get it:
37
+
38
+ ```python
39
+ from mloda.user import PluginLoader, mloda
40
+ PluginLoader.all()
41
+
42
+ result = mloda.run_all(
43
+ features=["customer_id", "income", "income__sum_aggr", "age__avg_aggr"],
44
+ compute_frameworks=["PandasDataFrame"],
45
+ api_data={"SampleData": {
46
+ "customer_id": ["C001", "C002", "C003", "C004", "C005"],
47
+ "age": [25, 35, 45, 30, 50],
48
+ "income": [50000, 75000, 90000, 60000, 85000]
49
+ }}
50
+ )
51
+ ```
52
+
53
+ Copy, paste, run. mloda resolves dependencies, chains plugins, delivers data.
54
+
55
+ ---
56
+
57
+ ## What mloda Does
58
+
59
+ ```
60
+ ┌─────────────────────────────────────────────────────────────────┐
61
+ │ DATA USERS │
62
+ │ AI Agents • ML Pipelines • Data Science • Analytics │
63
+ └───────────────────────────┬─────────────────────────────────────┘
64
+ │ describe what they need
65
+
66
+ ┌───────────────┐
67
+ │ mloda │ ← resolves HOW from WHAT
68
+ │ [Plugins] │
69
+ └───────────────┘
70
+ │ delivers trusted data
71
+
72
+ ┌─────────────────────────────────────────────────────────────────┐
73
+ │ DATA SOURCES │
74
+ │ Databases • APIs • Files • Any source via plugins │
75
+ └─────────────────────────────────────────────────────────────────┘
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Why mloda?
81
+
82
+ | You want to... | mloda gives you... |
83
+ |----------------|-------------------|
84
+ | Give AI agents data access | Declarative API - agents describe WHAT, not HOW |
85
+ | Trace every result | Built-in lineage back to source |
86
+ | Reuse across projects | Plugins work anywhere - notebook to production |
87
+ | Mix data sources | One interface for DBs, APIs, files, anything |
88
+
89
+ ---
90
+
91
+ ## AI Use Case: LLM Tool Function
92
+
93
+ Let LLMs request data without writing code:
94
+
95
+ ```python
96
+ # LLM generates this JSON
97
+ llm_request = '["customer_id", {"name": "income__sum_aggr"}]'
98
+
99
+ # mloda executes it
100
+ from mloda.user import load_features_from_config
101
+ features = load_features_from_config(llm_request, format="json")
102
+ result = mloda.run_all(
103
+ features=features,
104
+ compute_frameworks=["PandasDataFrame"],
105
+ api_data={"SampleData": {"customer_id": ["C001", "C002"], "income": [50000, 75000]}}
106
+ )
107
+ ```
108
+
109
+ More patterns: [Context Window Assembly](#2-context-window-assembly) • [RAG Pipelines](#3-rag-with-feature-chaining)
110
+
111
+ ---
112
+
113
+ ## How mloda is Different
114
+
115
+ mloda separates **WHAT** you need from **HOW** to get it - through plugins. Existing tools solve parts of this, but none bridge the full gap:
116
+
117
+ | Category | Products | What it does | Why it's not enough |
118
+ |----------|----------|--------------|---------------------|
119
+ | Feature Stores | Feast, Tecton, Featureform | Store + serve features | Infrastructure-tied, storage-only |
120
+ | Semantic Layers | dbt Semantic Layer, Cube | Declarative metrics | SQL-only, centralized |
121
+ | DAG Frameworks | Hamilton, Kedro | Dataflows as code | Function-first, no plugin abstraction |
122
+ | Data Catalogs | DataHub, Atlan | Metadata & discovery | No execution, no contracts |
123
+ | ORMs | SQLAlchemy, Django ORM | Database abstraction | Single database, no ML lifecycle |
124
+
125
+ **mloda is the connection layer** - separating WHAT you compute from HOW you compute it. Plugins define transformations. Users describe requirements. mloda resolves the pipeline.
126
+
127
+ ---
128
+
129
+ ## Plugins: The Building Blocks
130
+
131
+ mloda's architecture follows three roles: **providers** (define plugins), **users** (access data), and **stewards** (govern execution). The module structure reflects this: `mloda.provider`, `mloda.user`, `mloda.steward`.
132
+
133
+ mloda uses three types of plugins:
134
+
135
+ | Type | What it does |
136
+ |------|--------------|
137
+ | **FeatureGroup** | Defines data transformations |
138
+ | **ComputeFramework** | Execution backend (Pandas, Spark, etc.) |
139
+ | **Extender** | Hooks for logging, validation, monitoring |
140
+
141
+ Most of the time, you'll work with **FeatureGroups** - Python classes that define how to access and transform data (see Quick Example above).
142
+
143
+ **Why plugins?**
144
+ - **Steps, not pipelines** - Build transformations. mloda wires them together.
145
+ - **Small and testable** - Each plugin is a focused unit. Easy to test, easy to debug.
146
+ - **AI-friendly** - Small, template-like structures. Let AI generate plugins for you.
147
+ - **Share what isn't secret** - Your pipeline runs steps a,b,c,d. Steps b,c,d have no proprietary logic? Share them across projects, teams, even organizations.
148
+ - **Experiment to production** - Same plugins in your notebook and your cluster. No rewrite.
149
+ - **Stand on shoulders** - Combine community plugins with your own. Build on what exists.
150
+
151
+ ---
152
+
153
+ ## AI Use Case Patterns
154
+
155
+ ### 1. LLM Tool Function
156
+
157
+ Give LLMs deterministic data access - they declare what, mloda handles how:
158
+
159
+ ```python
160
+ from mloda.user import PluginLoader, load_features_from_config, mloda
161
+ PluginLoader.all()
162
+
163
+ # LLM generates this JSON (no Python code needed)
164
+ llm_output = '''
165
+ [
166
+ "customer_id",
167
+ {"name": "income__sum_aggr"},
168
+ {"name": "age__avg_aggr"},
169
+ {"name": "total_spend", "options": {"aggregation_type": "sum", "in_features": "income"}}
170
+ ]
171
+ '''
172
+
173
+ # mloda parses JSON into Feature objects
174
+ features = load_features_from_config(llm_output, format="json")
175
+
176
+ result = mloda.run_all(
177
+ features=features,
178
+ compute_frameworks=["PandasDataFrame"],
179
+ api_data={"SampleData": {
180
+ "customer_id": ["C001", "C002", "C003"],
181
+ "income": [50000, 75000, 90000],
182
+ "age": [25, 35, 45]
183
+ }}
184
+ )
185
+ ```
186
+
187
+ **LLM-friendly:** The agent only declares what it needs - mloda handles the rest.
188
+
189
+ ### 2. Context Window Assembly
190
+
191
+ Gather context from multiple sources declaratively - mloda validates and delivers. Why not let an AI agent do it?
192
+
193
+ *Example: This shows the API pattern. Requires custom FeatureGroup implementations for your data sources.*
194
+
195
+ ```python
196
+ from mloda.user import Feature, mloda
197
+
198
+ # Build complete context from multiple sources
199
+ features = [
200
+ Feature(name="system_instructions", options={"template": "support_agent"}),
201
+ Feature(name="user_profile", options={"user_id": user_id, "include_preferences": True}),
202
+ Feature(name="knowledge_base", options={"query": user_query, "top_k": 5}),
203
+ Feature(name="conversation_history", options={"limit": 20, "summarize_old": True}),
204
+ Feature(name="available_tools", options={"category": "customer_service"}),
205
+ Feature(name="output_format", options={"format": "markdown", "max_length": 500}),
206
+ ]
207
+
208
+ result = mloda.run_all(
209
+ features=features,
210
+ compute_frameworks=["PythonDictFramework"],
211
+ api_data={"UserQuery": {"query": [user_query]}}
212
+ )
213
+
214
+ # Each feature resolved via its plugin, validated
215
+ ```
216
+
217
+ ### 3. RAG with Feature Chaining
218
+
219
+ Build RAG pipelines declaratively - mloda chains the steps for you.
220
+
221
+ *Example: This shows the chaining syntax. Requires custom FeatureGroup implementations for retrieval and processing.*
222
+
223
+ ```python
224
+ # String-based chaining: query -> validate -> retrieve -> redact
225
+ Feature(name="user_query__injection_checked__retrieved__pii_redacted")
226
+
227
+ # Configuration-based chaining: explicit pipeline
228
+ Feature(
229
+ name="safe_context",
230
+ options=Options(context={
231
+ "in_features": "documents__retrieved__pii_redacted",
232
+ "redact_types": ["email", "phone", "ssn"]
233
+ })
234
+ )
235
+ ```
236
+
237
+ mloda resolves the full chain - you declare the end result, not the steps.
238
+
239
+ **Automatic dependency resolution:** You only declare what you need. If `pii_redacted` depends on `retrieved` which depends on `documents`, just ask for `pii_redacted` - mloda traces back and resolves the full chain.
240
+
241
+ ---
242
+
243
+ ## Compute Frameworks
244
+
245
+ Mix multiple backends in a single pipeline - mloda routes each feature to the right framework:
246
+
247
+ ```python
248
+ result = mloda.run_all(
249
+ features=[...],
250
+ compute_frameworks=["PandasDataFrame", "PolarsDataFrame", "SparkFramework"]
251
+ )
252
+
253
+ # Results may come from different frameworks based on plugin compatibility
254
+ ```
255
+
256
+ Add your own frameworks - mloda is extensible.
257
+
258
+ ---
259
+
260
+ ## Extenders
261
+
262
+ Wrap plugin execution for logging, validation, or lineage tracking:
263
+
264
+ ```python
265
+ import time
266
+ from mloda.steward import Extender, ExtenderHook
267
+
268
+ class LogExecutionTime(Extender):
269
+ def wraps(self):
270
+ return {ExtenderHook.FEATURE_GROUP_CALCULATE_FEATURE}
271
+
272
+ def __call__(self, func, *args, **kwargs):
273
+ start = time.time()
274
+ result = func(*args, **kwargs)
275
+ print(f"Took {time.time() - start:.2f}s")
276
+ return result
277
+
278
+ # Use it
279
+ result = mloda.run_all(features, function_extender={LogExecutionTime()})
280
+ ```
281
+
282
+ Built-in and custom extenders give you full lineage - trace any result back to its source.
283
+
284
+ ---
285
+
286
+ ## When to Use mloda
287
+
288
+ **Use mloda when:**
289
+ - Your agents need data from multiple sources
290
+ - You want consistent, validated data access
291
+ - You need traceability (audit, debugging)
292
+ - Multiple agents share the same data patterns
293
+
294
+ **Don't use mloda for:**
295
+ - Single database, simple queries → use an ORM
296
+ - One-off scripts → just write the code
297
+ - Real-time streaming (<5ms) → use Kafka/Flink
298
+
299
+ ---
300
+
301
+ ## Documentation
302
+
303
+ - **[Getting Started](https://mloda-ai.github.io/mloda/chapter1/installation/)** - Installation and first steps
304
+ - **[Plugin Development](https://mloda-ai.github.io/mloda/chapter1/feature-groups/)** - Build your own plugins
305
+ - **[API Reference](https://mloda-ai.github.io/mloda/in_depth/mloda-api/)** - Complete API docs
306
+
307
+ ---
308
+
309
+ ## Contributing
310
+
311
+ We welcome contributions! Build plugins, improve docs, or add features.
312
+
313
+ - **[GitHub Issues](https://github.com/mloda-ai/mloda/issues/)** - Report bugs or request features
314
+ - **[Development Guide](https://mloda-ai.github.io/mloda/development/)** - How to contribute