outerproduct 0.2.4__tar.gz → 0.3.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.
- {outerproduct-0.2.4 → outerproduct-0.3.0}/PKG-INFO +29 -15
- {outerproduct-0.2.4 → outerproduct-0.3.0}/README.md +25 -14
- {outerproduct-0.2.4 → outerproduct-0.3.0}/pyproject.toml +22 -9
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/__init__.py +155 -41
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/agentic/documents.py +36 -9
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/client/__init__.py +31 -8
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/client/_http.py +27 -1
- outerproduct-0.3.0/src/outerproduct/client/apis.py +487 -0
- outerproduct-0.3.0/src/outerproduct/client/client.py +442 -0
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/client/exceptions.py +3 -4
- outerproduct-0.3.0/src/outerproduct/connector.py +288 -0
- outerproduct-0.3.0/src/outerproduct/dataset/__init__.py +377 -0
- outerproduct-0.3.0/src/outerproduct/dataset/datatypes.py +122 -0
- outerproduct-0.3.0/src/outerproduct/expr.py +200 -0
- outerproduct-0.3.0/src/outerproduct/id.py +3 -0
- outerproduct-0.3.0/src/outerproduct/job.py +240 -0
- outerproduct-0.3.0/src/outerproduct/job_result.py +72 -0
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/model.py +312 -142
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/reasoning/__init__.py +127 -101
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/reasoning/pattern_tracker.py +74 -81
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/trainer.py +74 -157
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/utils.py +69 -3
- outerproduct-0.2.4/src/outerproduct/client/apis.py +0 -271
- outerproduct-0.2.4/src/outerproduct/client/client.py +0 -240
- outerproduct-0.2.4/src/outerproduct/client/endpoints.py +0 -292
- outerproduct-0.2.4/src/outerproduct/connector.py +0 -543
- outerproduct-0.2.4/src/outerproduct/dataset.py +0 -424
- outerproduct-0.2.4/src/outerproduct/job.py +0 -131
- {outerproduct-0.2.4 → outerproduct-0.3.0}/.gitignore +0 -0
- {outerproduct-0.2.4 → outerproduct-0.3.0}/LICENSE +0 -0
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/agentic/__init__.py +0 -0
- {outerproduct-0.2.4 → outerproduct-0.3.0}/src/outerproduct/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: outerproduct
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python SDK for the OuterProduct API
|
|
5
5
|
Project-URL: Documentation, https://docs.outerproduct.com
|
|
6
6
|
Project-URL: Homepage, https://outerproduct.com
|
|
@@ -21,11 +21,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
21
21
|
Classifier: Typing :: Typed
|
|
22
22
|
Requires-Python: >=3.12
|
|
23
23
|
Requires-Dist: httpx~=0.27
|
|
24
|
+
Requires-Dist: narwhals>=1.13
|
|
24
25
|
Requires-Dist: numpy~=1.23
|
|
25
26
|
Requires-Dist: outerproduct-http-types
|
|
26
27
|
Requires-Dist: pandas~=2.0
|
|
28
|
+
Requires-Dist: polars<=1.41.0
|
|
27
29
|
Requires-Dist: pydantic<3,>=2.0
|
|
28
30
|
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: anyio; extra == 'dev'
|
|
29
32
|
Requires-Dist: pyarrow>=14.0; extra == 'dev'
|
|
30
33
|
Requires-Dist: pytest; extra == 'dev'
|
|
31
34
|
Requires-Dist: respx; extra == 'dev'
|
|
@@ -34,10 +37,17 @@ Requires-Dist: pyarrow>=14.0; extra == 'parquet'
|
|
|
34
37
|
Description-Content-Type: text/markdown
|
|
35
38
|
|
|
36
39
|
# outerproduct-sdk
|
|
40
|
+
|
|
37
41
|
Python SDK for training and explaining tabular ML models with the OuterProduct API.
|
|
38
42
|
|
|
39
43
|
## Installation
|
|
40
44
|
|
|
45
|
+
```bash
|
|
46
|
+
uv add outerproduct
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or with pip:
|
|
50
|
+
|
|
41
51
|
```bash
|
|
42
52
|
pip install outerproduct
|
|
43
53
|
```
|
|
@@ -51,11 +61,10 @@ import outerproduct as op
|
|
|
51
61
|
op.init(api_key="op_live_...")
|
|
52
62
|
|
|
53
63
|
# 2. Upload data
|
|
54
|
-
|
|
55
|
-
dataset = connector.upload("credit.csv", label_column="default")
|
|
64
|
+
dataset = op.LocalDataset.from_csv("credit.csv").upload()
|
|
56
65
|
|
|
57
|
-
# 3. Train a reasoning model
|
|
58
|
-
rm = op.reasoning.fit(dataset)
|
|
66
|
+
# 3. Train a reasoning model (returns a job handle; .wait() blocks for the model)
|
|
67
|
+
rm = op.reasoning.fit(dataset).wait()
|
|
59
68
|
|
|
60
69
|
# 4. Predict
|
|
61
70
|
predictions = rm.predict(X_new)
|
|
@@ -110,7 +119,7 @@ schema = DocumentSchema(
|
|
|
110
119
|
|
|
111
120
|
```python
|
|
112
121
|
trainer = op.Trainer.configure(dataset, model_types=["tabm", "xgboost"])
|
|
113
|
-
model = trainer.run(strategy="random")
|
|
122
|
+
model = trainer.run(strategy="random").wait()
|
|
114
123
|
predictions = model.predict(X_new)
|
|
115
124
|
```
|
|
116
125
|
|
|
@@ -124,14 +133,14 @@ teacher = op.model.Predictor(
|
|
|
124
133
|
)
|
|
125
134
|
|
|
126
135
|
# Distill into a reasoning model
|
|
127
|
-
rm = op.reasoning.fit(dataset, teacher=teacher)
|
|
136
|
+
rm = op.reasoning.fit(dataset, teacher=teacher).wait()
|
|
128
137
|
```
|
|
129
138
|
|
|
130
139
|
You can also distill an existing OuterProduct-trained model:
|
|
131
140
|
|
|
132
141
|
```python
|
|
133
|
-
trained = op.Trainer.configure(dataset).run()
|
|
134
|
-
rm = op.reasoning.fit(dataset, teacher=trained)
|
|
142
|
+
trained = op.Trainer.configure(dataset).run().wait()
|
|
143
|
+
rm = op.reasoning.fit(dataset, teacher=trained).wait()
|
|
135
144
|
```
|
|
136
145
|
|
|
137
146
|
### Reasoning Model Operations
|
|
@@ -151,16 +160,23 @@ trace = rm.scenario(X, target_class=1, max_steps=10)
|
|
|
151
160
|
|
|
152
161
|
# Pattern aggregation across rejected predictions
|
|
153
162
|
import outerproduct as op
|
|
154
|
-
pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None))
|
|
163
|
+
pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None)).wait()
|
|
155
164
|
pt.distribution(X_new)
|
|
156
165
|
```
|
|
157
166
|
|
|
158
|
-
###
|
|
167
|
+
### Jobs
|
|
159
168
|
|
|
160
|
-
|
|
169
|
+
Every async server operation — `Trainer.run()`, `reasoning.fit()`,
|
|
170
|
+
`pattern_tracker.fit()` — submits the work and returns a typed job handle
|
|
171
|
+
(`TrainerJob`, `ReasoningFitJob`, `PatternTrackerJob`). Drive it however you
|
|
172
|
+
need:
|
|
161
173
|
|
|
162
174
|
```python
|
|
163
|
-
|
|
175
|
+
job = op.reasoning.fit(dataset)
|
|
176
|
+
|
|
177
|
+
job.status() # "pending" | "running" | "completed" | "failed"
|
|
178
|
+
job.results() # typed JobResult once finished (raises JobFailed on failure)
|
|
179
|
+
rm = job.wait() # block until completion, return the trained ReasoningModel
|
|
164
180
|
```
|
|
165
181
|
|
|
166
182
|
### Configuration
|
|
@@ -175,7 +191,5 @@ op.init()
|
|
|
175
191
|
op.init(
|
|
176
192
|
api_key="op_live_...",
|
|
177
193
|
base_url="https://api.outerproduct.com",
|
|
178
|
-
hardware=op.ModalHardware(),
|
|
179
194
|
)
|
|
180
195
|
```
|
|
181
|
-
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# outerproduct-sdk
|
|
2
|
+
|
|
2
3
|
Python SDK for training and explaining tabular ML models with the OuterProduct API.
|
|
3
4
|
|
|
4
5
|
## Installation
|
|
5
6
|
|
|
7
|
+
```bash
|
|
8
|
+
uv add outerproduct
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with pip:
|
|
12
|
+
|
|
6
13
|
```bash
|
|
7
14
|
pip install outerproduct
|
|
8
15
|
```
|
|
@@ -16,11 +23,10 @@ import outerproduct as op
|
|
|
16
23
|
op.init(api_key="op_live_...")
|
|
17
24
|
|
|
18
25
|
# 2. Upload data
|
|
19
|
-
|
|
20
|
-
dataset = connector.upload("credit.csv", label_column="default")
|
|
26
|
+
dataset = op.LocalDataset.from_csv("credit.csv").upload()
|
|
21
27
|
|
|
22
|
-
# 3. Train a reasoning model
|
|
23
|
-
rm = op.reasoning.fit(dataset)
|
|
28
|
+
# 3. Train a reasoning model (returns a job handle; .wait() blocks for the model)
|
|
29
|
+
rm = op.reasoning.fit(dataset).wait()
|
|
24
30
|
|
|
25
31
|
# 4. Predict
|
|
26
32
|
predictions = rm.predict(X_new)
|
|
@@ -75,7 +81,7 @@ schema = DocumentSchema(
|
|
|
75
81
|
|
|
76
82
|
```python
|
|
77
83
|
trainer = op.Trainer.configure(dataset, model_types=["tabm", "xgboost"])
|
|
78
|
-
model = trainer.run(strategy="random")
|
|
84
|
+
model = trainer.run(strategy="random").wait()
|
|
79
85
|
predictions = model.predict(X_new)
|
|
80
86
|
```
|
|
81
87
|
|
|
@@ -89,14 +95,14 @@ teacher = op.model.Predictor(
|
|
|
89
95
|
)
|
|
90
96
|
|
|
91
97
|
# Distill into a reasoning model
|
|
92
|
-
rm = op.reasoning.fit(dataset, teacher=teacher)
|
|
98
|
+
rm = op.reasoning.fit(dataset, teacher=teacher).wait()
|
|
93
99
|
```
|
|
94
100
|
|
|
95
101
|
You can also distill an existing OuterProduct-trained model:
|
|
96
102
|
|
|
97
103
|
```python
|
|
98
|
-
trained = op.Trainer.configure(dataset).run()
|
|
99
|
-
rm = op.reasoning.fit(dataset, teacher=trained)
|
|
104
|
+
trained = op.Trainer.configure(dataset).run().wait()
|
|
105
|
+
rm = op.reasoning.fit(dataset, teacher=trained).wait()
|
|
100
106
|
```
|
|
101
107
|
|
|
102
108
|
### Reasoning Model Operations
|
|
@@ -116,16 +122,23 @@ trace = rm.scenario(X, target_class=1, max_steps=10)
|
|
|
116
122
|
|
|
117
123
|
# Pattern aggregation across rejected predictions
|
|
118
124
|
import outerproduct as op
|
|
119
|
-
pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None))
|
|
125
|
+
pt = op.reasoning.pattern_tracker.fit(rm, dataset, target_range=(0.5, None)).wait()
|
|
120
126
|
pt.distribution(X_new)
|
|
121
127
|
```
|
|
122
128
|
|
|
123
|
-
###
|
|
129
|
+
### Jobs
|
|
124
130
|
|
|
125
|
-
|
|
131
|
+
Every async server operation — `Trainer.run()`, `reasoning.fit()`,
|
|
132
|
+
`pattern_tracker.fit()` — submits the work and returns a typed job handle
|
|
133
|
+
(`TrainerJob`, `ReasoningFitJob`, `PatternTrackerJob`). Drive it however you
|
|
134
|
+
need:
|
|
126
135
|
|
|
127
136
|
```python
|
|
128
|
-
|
|
137
|
+
job = op.reasoning.fit(dataset)
|
|
138
|
+
|
|
139
|
+
job.status() # "pending" | "running" | "completed" | "failed"
|
|
140
|
+
job.results() # typed JobResult once finished (raises JobFailed on failure)
|
|
141
|
+
rm = job.wait() # block until completion, return the trained ReasoningModel
|
|
129
142
|
```
|
|
130
143
|
|
|
131
144
|
### Configuration
|
|
@@ -140,7 +153,5 @@ op.init()
|
|
|
140
153
|
op.init(
|
|
141
154
|
api_key="op_live_...",
|
|
142
155
|
base_url="https://api.outerproduct.com",
|
|
143
|
-
hardware=op.ModalHardware(),
|
|
144
156
|
)
|
|
145
157
|
```
|
|
146
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "outerproduct"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Python SDK for the OuterProduct API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -25,10 +25,12 @@ classifiers = [
|
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
27
|
"httpx~=0.27",
|
|
28
|
+
"narwhals>=1.13",
|
|
28
29
|
"numpy~=1.23",
|
|
29
30
|
"outerproduct-http-types",
|
|
30
31
|
"pandas~=2.0",
|
|
31
|
-
"
|
|
32
|
+
"polars<=1.41.0",
|
|
33
|
+
"pydantic>=2.0,<3",
|
|
32
34
|
]
|
|
33
35
|
|
|
34
36
|
[project.urls]
|
|
@@ -36,11 +38,12 @@ Documentation = "https://docs.outerproduct.com"
|
|
|
36
38
|
Homepage = "https://outerproduct.com"
|
|
37
39
|
|
|
38
40
|
[project.optional-dependencies]
|
|
39
|
-
dev = ["pytest", "respx", "pyarrow>=14.0"]
|
|
41
|
+
dev = ["anyio", "pytest", "respx", "pyarrow>=14.0"]
|
|
40
42
|
parquet = ["pyarrow>=14.0"]
|
|
41
43
|
|
|
42
44
|
[dependency-groups]
|
|
43
45
|
dev = [
|
|
46
|
+
"anyio>=4.0",
|
|
44
47
|
"prek>=0.3.11",
|
|
45
48
|
"pyarrow>=14.0",
|
|
46
49
|
"pytest>=9.0.3",
|
|
@@ -66,7 +69,7 @@ include = [
|
|
|
66
69
|
]
|
|
67
70
|
|
|
68
71
|
[tool.hatch.envs.hatch-test]
|
|
69
|
-
extra-dependencies = ["respx", "pyarrow>=14.0"]
|
|
72
|
+
extra-dependencies = ["anyio", "respx", "pyarrow>=14.0"]
|
|
70
73
|
|
|
71
74
|
[[tool.hatch.envs.hatch-test.matrix]]
|
|
72
75
|
python = ["3.12", "3.13", "3.14"]
|
|
@@ -108,8 +111,18 @@ exclude = [".agents/**", ".ruff_cache/**", ".venv/**"]
|
|
|
108
111
|
[tool.ty.src]
|
|
109
112
|
include = ["src"]
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
|
|
114
|
+
# By default the SDK resolves outerproduct-http-types from PyPI, via the plain
|
|
115
|
+
# `outerproduct-http-types` entry in [project].dependencies above — pure-PyPI,
|
|
116
|
+
# nothing to configure and no token needed for `uv sync`.
|
|
117
|
+
#
|
|
118
|
+
# Co-developing http-types alongside the SDK? Uncomment [tool.uv.sources] and
|
|
119
|
+
# ONE override below to resolve it from source instead. This is a uv-only
|
|
120
|
+
# setting that is stripped at build time, so it never affects the published
|
|
121
|
+
# wheel. NOTE: with the git form, check-main-pins.py requires `branch = "main"`
|
|
122
|
+
# on merges to main — don't commit a non-main pin (or the local path) to main.
|
|
123
|
+
#
|
|
124
|
+
# [tool.uv.sources]
|
|
125
|
+
# # tip of http-types' default branch (CI git-auth is set up for this private repo):
|
|
126
|
+
# outerproduct-http-types = { git = "https://github.com/OuterProduct-ai/outerproduct-http-types.git", branch = "main" }
|
|
127
|
+
# # ...or a local editable checkout for live co-editing:
|
|
128
|
+
# outerproduct-http-types = { path = "../outerproduct-http-types", editable = true }
|
|
@@ -4,9 +4,10 @@ The package exposes a small, flat surface organised around the following
|
|
|
4
4
|
core concepts:
|
|
5
5
|
|
|
6
6
|
- :class:`~outerproduct.dataset.Dataset` — a tabular dataset with an optional label column and
|
|
7
|
-
per-column schema (:class:`~
|
|
8
|
-
- :class:`~outerproduct.connector.Connector` —
|
|
9
|
-
:class:`~outerproduct.connector.S3Connector`, …) that produces a :class:`~outerproduct.dataset.Dataset
|
|
7
|
+
per-column schema (:class:`~outerproduct_http_types.DatasetColumn`).
|
|
8
|
+
- :class:`~outerproduct.connector.Connector` — an *external* source of data
|
|
9
|
+
(:class:`~outerproduct.connector.S3Connector`, …) that produces a :class:`~outerproduct.dataset.Dataset`;
|
|
10
|
+
local files and in-memory frames are staged via :meth:`~outerproduct.dataset.LocalDataset.upload` instead.
|
|
10
11
|
- :class:`~outerproduct.trainer.Trainer` — orchestrates general-purpose training and
|
|
11
12
|
returns a :class:`~outerproduct.model.Model` via :meth:`~outerproduct.trainer.Trainer.configure` and
|
|
12
13
|
:meth:`~outerproduct.trainer.Trainer.run`.
|
|
@@ -22,7 +23,7 @@ core concepts:
|
|
|
22
23
|
aggregates the per-row local-rule explanations a
|
|
23
24
|
:class:`~outerproduct.model.ReasoningModel` produces over a target
|
|
24
25
|
prediction band into a small set of executable filter patterns.
|
|
25
|
-
- :class:`~outerproduct.reasoning.Reasoning` / :class:`~outerproduct.reasoning.
|
|
26
|
+
- :class:`~outerproduct.reasoning.Reasoning` / :class:`~outerproduct.reasoning.ScenarioResult` — the typed results of
|
|
26
27
|
reasoning calls.
|
|
27
28
|
|
|
28
29
|
Quickstart
|
|
@@ -32,9 +33,8 @@ Quickstart
|
|
|
32
33
|
import outerproduct as op
|
|
33
34
|
|
|
34
35
|
op.init(api_key="...")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
reasoning_model = op.reasoning.fit(dataset, label_column="churn")
|
|
36
|
+
dataset = op.LocalDataset.from_csv("customers.csv").upload()
|
|
37
|
+
reasoning_model = op.reasoning.fit(dataset, task=op.BinclassConfig(label_column="churn"))
|
|
38
38
|
test_dataset = op.Dataset(X_new)
|
|
39
39
|
predictions = reasoning_model.predict(test_dataset)
|
|
40
40
|
reasoning = reasoning_model.explain(test_dataset)
|
|
@@ -48,31 +48,55 @@ from dataclasses import dataclass, field
|
|
|
48
48
|
from importlib.metadata import version as _pkg_version
|
|
49
49
|
from typing import TYPE_CHECKING, Generator
|
|
50
50
|
|
|
51
|
-
from outerproduct_http_types import
|
|
51
|
+
from outerproduct_http_types import (
|
|
52
|
+
BinclassConfig,
|
|
53
|
+
ConnectorType,
|
|
54
|
+
ForecastingConfig,
|
|
55
|
+
MulticlassConfig,
|
|
56
|
+
RegressionConfig,
|
|
57
|
+
SequenceConfig,
|
|
58
|
+
TaskConfig,
|
|
59
|
+
TaskKind,
|
|
60
|
+
)
|
|
52
61
|
|
|
53
62
|
from outerproduct import agentic
|
|
54
63
|
from outerproduct.connector import (
|
|
55
64
|
Connector,
|
|
56
65
|
DatabricksConnector,
|
|
57
|
-
FileUploadConnector,
|
|
58
66
|
S3Connector,
|
|
59
67
|
SnowflakeConnector,
|
|
60
68
|
)
|
|
61
|
-
from outerproduct.dataset import
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
from outerproduct.dataset import (
|
|
70
|
+
Dataset,
|
|
71
|
+
DatasetColumn,
|
|
72
|
+
Datatype,
|
|
73
|
+
LocalDataset,
|
|
74
|
+
)
|
|
75
|
+
from outerproduct.expr import Expr, col
|
|
76
|
+
from outerproduct.job import Job, JobFailed, JobNotReady, ReasoningFitJob, TrainerJob
|
|
77
|
+
from outerproduct.job_result import JobResult
|
|
78
|
+
from outerproduct.model import (
|
|
79
|
+
DiffModel,
|
|
80
|
+
Model,
|
|
81
|
+
ReasoningModel,
|
|
82
|
+
SequenceReasoningModel,
|
|
83
|
+
)
|
|
64
84
|
from outerproduct import reasoning
|
|
65
|
-
from outerproduct.reasoning import
|
|
85
|
+
from outerproduct.reasoning import (
|
|
86
|
+
Change,
|
|
87
|
+
Counterfactual,
|
|
88
|
+
QueryResult,
|
|
89
|
+
Reasoning,
|
|
90
|
+
ScenarioResult,
|
|
91
|
+
)
|
|
66
92
|
from outerproduct.reasoning import pattern_tracker # noqa: F401 — makes op.reasoning.pattern_tracker accessible
|
|
67
93
|
from outerproduct.trainer import (
|
|
68
|
-
Hardware,
|
|
69
94
|
Metric,
|
|
70
|
-
ModalHardware,
|
|
71
95
|
Trainer,
|
|
72
96
|
)
|
|
73
97
|
|
|
74
98
|
if TYPE_CHECKING:
|
|
75
|
-
from outerproduct.client import OuterProductClient
|
|
99
|
+
from outerproduct.client import AsyncOuterProductClient, OuterProductClient
|
|
76
100
|
|
|
77
101
|
try:
|
|
78
102
|
__version__ = _pkg_version("outerproduct")
|
|
@@ -86,7 +110,6 @@ class _GlobalState:
|
|
|
86
110
|
|
|
87
111
|
api_key: str | None = None
|
|
88
112
|
base_url: str | None = None
|
|
89
|
-
hardware: Hardware | None = None
|
|
90
113
|
extra: dict[str, object] = field(default_factory=dict)
|
|
91
114
|
|
|
92
115
|
|
|
@@ -111,6 +134,14 @@ _request_client: ContextVar["OuterProductClient | None"] = ContextVar(
|
|
|
111
134
|
"outerproduct_request_client", default=None
|
|
112
135
|
)
|
|
113
136
|
|
|
137
|
+
# The async mirror. Sync (`use_client`) and async (`use_aclient`) injection
|
|
138
|
+
# use *separate* contextvars so the two client colors can never be confused:
|
|
139
|
+
# the async transport needs an `AsyncOuterProductClient`, the sync transport an
|
|
140
|
+
# `OuterProductClient`, and a sync helper must never pick up an async client.
|
|
141
|
+
_arequest_client: ContextVar["AsyncOuterProductClient | None"] = ContextVar(
|
|
142
|
+
"outerproduct_arequest_client", default=None
|
|
143
|
+
)
|
|
144
|
+
|
|
114
145
|
|
|
115
146
|
@contextmanager
|
|
116
147
|
def use_client(
|
|
@@ -148,24 +179,49 @@ def use_client(
|
|
|
148
179
|
_request_client.reset(token)
|
|
149
180
|
|
|
150
181
|
|
|
182
|
+
@contextmanager
|
|
183
|
+
def use_aclient(
|
|
184
|
+
client: "AsyncOuterProductClient",
|
|
185
|
+
) -> Generator[None, None, None]:
|
|
186
|
+
"""Async mirror of :func:`use_client` — inject an :class:`AsyncOuterProductClient`.
|
|
187
|
+
|
|
188
|
+
Inside this block, the SDK's async HTTP helpers (``_aclient`` /
|
|
189
|
+
``_aresolve_client``, and therefore every ``a``-prefixed/async SDK call)
|
|
190
|
+
use ``client`` instead of building one from ``op._state``. The caller owns
|
|
191
|
+
``client``'s lifecycle — the SDK will not ``aclose`` it.
|
|
192
|
+
|
|
193
|
+
This is itself an ordinary (synchronous) context manager — it only sets a
|
|
194
|
+
contextvar and does no I/O — so it can be entered with a plain ``with``
|
|
195
|
+
even though the client it injects is async::
|
|
196
|
+
|
|
197
|
+
async with AsyncOuterProductClient.from_credentials(base, token) as client:
|
|
198
|
+
with op.use_aclient(client):
|
|
199
|
+
await op.reasoning.afit(ds, task=...)
|
|
200
|
+
"""
|
|
201
|
+
token = _arequest_client.set(client)
|
|
202
|
+
try:
|
|
203
|
+
yield
|
|
204
|
+
finally:
|
|
205
|
+
_arequest_client.reset(token)
|
|
206
|
+
|
|
207
|
+
|
|
151
208
|
def init(
|
|
152
209
|
api_key: str | None = None,
|
|
153
210
|
*,
|
|
154
211
|
base_url: str | None = None,
|
|
155
|
-
hardware: Hardware | None = None,
|
|
156
212
|
) -> None:
|
|
157
213
|
"""Configure the OuterProduct SDK for the current Python process.
|
|
158
214
|
|
|
159
215
|
Call once near the top of your program. Subsequent operations
|
|
160
|
-
(training, prediction, uploads) read credentials
|
|
161
|
-
|
|
216
|
+
(training, prediction, uploads) read credentials and the API base URL
|
|
217
|
+
from the state recorded here.
|
|
162
218
|
|
|
163
219
|
Examples
|
|
164
220
|
--------
|
|
165
221
|
.. code-block:: python
|
|
166
222
|
|
|
167
223
|
import outerproduct as op
|
|
168
|
-
op.init(api_key="op_live_..."
|
|
224
|
+
op.init(api_key="op_live_...")
|
|
169
225
|
|
|
170
226
|
Parameters
|
|
171
227
|
----------
|
|
@@ -176,23 +232,28 @@ def init(
|
|
|
176
232
|
base_url : str, optional
|
|
177
233
|
Override for the API base URL. If omitted, the SDK uses the
|
|
178
234
|
production endpoint.
|
|
179
|
-
hardware : Hardware, optional
|
|
180
|
-
Default execution backend used by :class:`~outerproduct.trainer.Trainer`
|
|
181
|
-
and :func:`~outerproduct.reasoning.fit`. If omitted, the API
|
|
182
|
-
server falls back to a managed Modal worker.
|
|
183
235
|
"""
|
|
184
236
|
_state.api_key = api_key
|
|
185
237
|
_state.base_url = base_url
|
|
186
|
-
_state.hardware = hardware
|
|
187
238
|
|
|
188
239
|
|
|
189
240
|
def get_datasets() -> list[Dataset]:
|
|
190
|
-
"""List datasets owned by the configured
|
|
191
|
-
from outerproduct.client import OuterProductClient
|
|
241
|
+
"""List datasets owned by the configured caller.
|
|
192
242
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
243
|
+
Honors :func:`use_client` — multi-tenant consumers (e.g. an MCP
|
|
244
|
+
server) get per-user authentication via the request-scoped client
|
|
245
|
+
contextvar. Falls back to the process-global API key otherwise.
|
|
246
|
+
"""
|
|
247
|
+
from outerproduct.utils import _resolve_client
|
|
248
|
+
|
|
249
|
+
client = _resolve_client()
|
|
250
|
+
response = client.datasets_api.list()
|
|
251
|
+
out: list[Dataset] = []
|
|
252
|
+
for entry in response.datasets:
|
|
253
|
+
dataset = Dataset(entry.connection_config, client, entry.columns)
|
|
254
|
+
dataset._stamp_id(entry.id)
|
|
255
|
+
out.append(dataset)
|
|
256
|
+
return out
|
|
196
257
|
|
|
197
258
|
|
|
198
259
|
def get_dataset(dataset_id: str) -> Dataset:
|
|
@@ -201,45 +262,98 @@ def get_dataset(dataset_id: str) -> Dataset:
|
|
|
201
262
|
The returned Dataset has its ``_id`` and ``connection_config`` already
|
|
202
263
|
stamped, so subsequent fit/predict calls short-circuit the server-side
|
|
203
264
|
create.
|
|
265
|
+
|
|
266
|
+
Honors :func:`use_client` — multi-tenant consumers (e.g. an MCP
|
|
267
|
+
server) get per-user authentication via the request-scoped client
|
|
268
|
+
contextvar. Falls back to the process-global API key otherwise.
|
|
269
|
+
"""
|
|
270
|
+
from outerproduct.utils import _resolve_client
|
|
271
|
+
|
|
272
|
+
client = _resolve_client()
|
|
273
|
+
entry = client.datasets_api.get(dataset_id)
|
|
274
|
+
dataset = Dataset(entry.connection_config, client, entry.columns)
|
|
275
|
+
dataset._stamp_id(entry.id)
|
|
276
|
+
return dataset
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def get_models() -> list["Model"]:
|
|
280
|
+
"""List models owned by the configured caller.
|
|
281
|
+
|
|
282
|
+
Rows tagged ``model_type='reasoning'`` come back as
|
|
283
|
+
:class:`~outerproduct.model.ReasoningModel`; trainer rows come back
|
|
284
|
+
as :class:`~outerproduct.model.Model`. Both expose ``id`` /
|
|
285
|
+
``dataset_id`` already stamped from the wire response; richer
|
|
286
|
+
attributes (``schema``, ``feature_names``) lazy-fetch on first use.
|
|
204
287
|
"""
|
|
205
|
-
from outerproduct.
|
|
288
|
+
from outerproduct.model import Model
|
|
289
|
+
from outerproduct.utils import _client
|
|
290
|
+
|
|
291
|
+
with _client() as client:
|
|
292
|
+
response = client.models_api.list()
|
|
293
|
+
return [Model._from_response(record) for record in response.models]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def get_model(model_id: str) -> "Model":
|
|
297
|
+
"""Fetch a single model by id; dispatches to Model / ReasoningModel
|
|
298
|
+
based on the row's ``model_type``."""
|
|
299
|
+
from outerproduct.model import Model
|
|
300
|
+
from outerproduct.utils import _client
|
|
206
301
|
|
|
207
|
-
with
|
|
208
|
-
record = client.
|
|
209
|
-
return
|
|
302
|
+
with _client() as client:
|
|
303
|
+
record = client.models_api.get(model_id)
|
|
304
|
+
return Model._from_response(record)
|
|
210
305
|
|
|
211
306
|
|
|
212
307
|
__all__ = [
|
|
213
308
|
# Bootstrap
|
|
214
309
|
"init",
|
|
215
310
|
"use_client",
|
|
311
|
+
"use_aclient",
|
|
216
312
|
"get_dataset",
|
|
217
313
|
"get_datasets",
|
|
314
|
+
"get_model",
|
|
315
|
+
"get_models",
|
|
218
316
|
"__version__",
|
|
219
317
|
# Dataset
|
|
220
318
|
"Dataset",
|
|
221
|
-
"
|
|
319
|
+
"LocalDataset",
|
|
320
|
+
"DatasetColumn",
|
|
321
|
+
"Datatype",
|
|
322
|
+
"col",
|
|
323
|
+
"Expr",
|
|
222
324
|
# Jobs
|
|
223
325
|
"Job",
|
|
326
|
+
"JobFailed",
|
|
327
|
+
"JobNotReady",
|
|
328
|
+
"JobResult",
|
|
329
|
+
"ReasoningFitJob",
|
|
224
330
|
"TrainerJob",
|
|
225
331
|
# Model
|
|
226
332
|
"Model",
|
|
227
333
|
"DiffModel",
|
|
228
334
|
"ReasoningModel",
|
|
229
|
-
"
|
|
335
|
+
"SequenceReasoningModel",
|
|
230
336
|
# Reasoning (submodule, accessed as op.reasoning.*)
|
|
231
337
|
"reasoning",
|
|
232
338
|
"Reasoning",
|
|
233
|
-
"
|
|
339
|
+
"ScenarioResult",
|
|
340
|
+
"QueryResult",
|
|
341
|
+
"Counterfactual",
|
|
342
|
+
"Change",
|
|
234
343
|
# Trainer
|
|
235
344
|
"Trainer",
|
|
236
|
-
"Hardware",
|
|
237
|
-
"ModalHardware",
|
|
238
345
|
"Metric",
|
|
346
|
+
# Task config
|
|
347
|
+
"TaskConfig",
|
|
348
|
+
"TaskKind",
|
|
349
|
+
"RegressionConfig",
|
|
350
|
+
"BinclassConfig",
|
|
351
|
+
"MulticlassConfig",
|
|
352
|
+
"ForecastingConfig",
|
|
353
|
+
"SequenceConfig",
|
|
239
354
|
# Connector
|
|
240
355
|
"Connector",
|
|
241
356
|
"ConnectorType",
|
|
242
|
-
"FileUploadConnector",
|
|
243
357
|
"S3Connector",
|
|
244
358
|
"SnowflakeConnector",
|
|
245
359
|
"DatabricksConnector",
|