outerproduct 0.2.3__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.
Potentially problematic release.
This version of outerproduct might be problematic. Click here for more details.
- {outerproduct-0.2.3 → outerproduct-0.3.0}/PKG-INFO +55 -15
- {outerproduct-0.2.3 → outerproduct-0.3.0}/README.md +51 -14
- {outerproduct-0.2.3 → outerproduct-0.3.0}/pyproject.toml +22 -9
- outerproduct-0.3.0/src/outerproduct/__init__.py +362 -0
- {outerproduct-0.2.3 → outerproduct-0.3.0}/src/outerproduct/agentic/documents.py +40 -13
- {outerproduct-0.2.3 → outerproduct-0.3.0}/src/outerproduct/client/__init__.py +31 -8
- {outerproduct-0.2.3 → 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.3 → 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.3 → outerproduct-0.3.0}/src/outerproduct/model.py +326 -139
- {outerproduct-0.2.3 → outerproduct-0.3.0}/src/outerproduct/reasoning/__init__.py +133 -101
- {outerproduct-0.2.3 → outerproduct-0.3.0}/src/outerproduct/reasoning/pattern_tracker.py +87 -66
- {outerproduct-0.2.3 → outerproduct-0.3.0}/src/outerproduct/trainer.py +78 -155
- {outerproduct-0.2.3 → outerproduct-0.3.0}/src/outerproduct/utils.py +90 -5
- outerproduct-0.2.3/src/outerproduct/__init__.py +0 -173
- outerproduct-0.2.3/src/outerproduct/client/apis.py +0 -319
- outerproduct-0.2.3/src/outerproduct/client/client.py +0 -240
- outerproduct-0.2.3/src/outerproduct/client/endpoints.py +0 -311
- outerproduct-0.2.3/src/outerproduct/connector.py +0 -745
- outerproduct-0.2.3/src/outerproduct/dataset.py +0 -460
- outerproduct-0.2.3/src/outerproduct/job.py +0 -125
- {outerproduct-0.2.3 → outerproduct-0.3.0}/.gitignore +0 -0
- {outerproduct-0.2.3 → outerproduct-0.3.0}/LICENSE +0 -0
- {outerproduct-0.2.3 → outerproduct-0.3.0}/src/outerproduct/agentic/__init__.py +0 -0
- {outerproduct-0.2.3 → 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)
|
|
@@ -80,11 +89,37 @@ df = pd.read_csv("credit.csv")
|
|
|
80
89
|
dataset = op.Dataset.from_pandas(df, label_column="default")
|
|
81
90
|
```
|
|
82
91
|
|
|
92
|
+
### Tabularize Documents into a Dataset
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from outerproduct.agentic import documents
|
|
96
|
+
|
|
97
|
+
docs = documents.DocumentSet.from_directory("./invoices")
|
|
98
|
+
refs = documents.upload(docs)
|
|
99
|
+
schema = documents.induce_schema(refs, use_case="fraud audit", skill="invoice")
|
|
100
|
+
dataset = documents.tabularize(refs, schema, label_column="is_fraud")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can also hand-author the schema:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from outerproduct.agentic.documents import DocumentSchema, DocumentQuestion, AnswerType
|
|
107
|
+
|
|
108
|
+
schema = DocumentSchema(
|
|
109
|
+
skill="invoice",
|
|
110
|
+
use_case="fraud audit",
|
|
111
|
+
questions=[
|
|
112
|
+
DocumentQuestion(id="invoice_total", question="Total amount due?", answer_type=AnswerType.NUMBER, unit="USD"),
|
|
113
|
+
DocumentQuestion(id="payment_terms", question="Payment terms?", answer_type=AnswerType.ENUM, enum=("net_15", "net_30", "net_60")),
|
|
114
|
+
],
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
83
118
|
### Train a Plain Model (No Explanations)
|
|
84
119
|
|
|
85
120
|
```python
|
|
86
121
|
trainer = op.Trainer.configure(dataset, model_types=["tabm", "xgboost"])
|
|
87
|
-
model = trainer.run(strategy="random")
|
|
122
|
+
model = trainer.run(strategy="random").wait()
|
|
88
123
|
predictions = model.predict(X_new)
|
|
89
124
|
```
|
|
90
125
|
|
|
@@ -98,14 +133,14 @@ teacher = op.model.Predictor(
|
|
|
98
133
|
)
|
|
99
134
|
|
|
100
135
|
# Distill into a reasoning model
|
|
101
|
-
rm = op.reasoning.fit(dataset, teacher=teacher)
|
|
136
|
+
rm = op.reasoning.fit(dataset, teacher=teacher).wait()
|
|
102
137
|
```
|
|
103
138
|
|
|
104
139
|
You can also distill an existing OuterProduct-trained model:
|
|
105
140
|
|
|
106
141
|
```python
|
|
107
|
-
trained = op.Trainer.configure(dataset).run()
|
|
108
|
-
rm = op.reasoning.fit(dataset, teacher=trained)
|
|
142
|
+
trained = op.Trainer.configure(dataset).run().wait()
|
|
143
|
+
rm = op.reasoning.fit(dataset, teacher=trained).wait()
|
|
109
144
|
```
|
|
110
145
|
|
|
111
146
|
### Reasoning Model Operations
|
|
@@ -125,16 +160,23 @@ trace = rm.scenario(X, target_class=1, max_steps=10)
|
|
|
125
160
|
|
|
126
161
|
# Pattern aggregation across rejected predictions
|
|
127
162
|
import outerproduct as op
|
|
128
|
-
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()
|
|
129
164
|
pt.distribution(X_new)
|
|
130
165
|
```
|
|
131
166
|
|
|
132
|
-
###
|
|
167
|
+
### Jobs
|
|
133
168
|
|
|
134
|
-
|
|
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:
|
|
135
173
|
|
|
136
174
|
```python
|
|
137
|
-
|
|
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
|
|
138
180
|
```
|
|
139
181
|
|
|
140
182
|
### Configuration
|
|
@@ -149,7 +191,5 @@ op.init()
|
|
|
149
191
|
op.init(
|
|
150
192
|
api_key="op_live_...",
|
|
151
193
|
base_url="https://api.outerproduct.com",
|
|
152
|
-
hardware=op.ModalHardware(),
|
|
153
194
|
)
|
|
154
195
|
```
|
|
155
|
-
|
|
@@ -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)
|
|
@@ -45,11 +51,37 @@ df = pd.read_csv("credit.csv")
|
|
|
45
51
|
dataset = op.Dataset.from_pandas(df, label_column="default")
|
|
46
52
|
```
|
|
47
53
|
|
|
54
|
+
### Tabularize Documents into a Dataset
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from outerproduct.agentic import documents
|
|
58
|
+
|
|
59
|
+
docs = documents.DocumentSet.from_directory("./invoices")
|
|
60
|
+
refs = documents.upload(docs)
|
|
61
|
+
schema = documents.induce_schema(refs, use_case="fraud audit", skill="invoice")
|
|
62
|
+
dataset = documents.tabularize(refs, schema, label_column="is_fraud")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
You can also hand-author the schema:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from outerproduct.agentic.documents import DocumentSchema, DocumentQuestion, AnswerType
|
|
69
|
+
|
|
70
|
+
schema = DocumentSchema(
|
|
71
|
+
skill="invoice",
|
|
72
|
+
use_case="fraud audit",
|
|
73
|
+
questions=[
|
|
74
|
+
DocumentQuestion(id="invoice_total", question="Total amount due?", answer_type=AnswerType.NUMBER, unit="USD"),
|
|
75
|
+
DocumentQuestion(id="payment_terms", question="Payment terms?", answer_type=AnswerType.ENUM, enum=("net_15", "net_30", "net_60")),
|
|
76
|
+
],
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
48
80
|
### Train a Plain Model (No Explanations)
|
|
49
81
|
|
|
50
82
|
```python
|
|
51
83
|
trainer = op.Trainer.configure(dataset, model_types=["tabm", "xgboost"])
|
|
52
|
-
model = trainer.run(strategy="random")
|
|
84
|
+
model = trainer.run(strategy="random").wait()
|
|
53
85
|
predictions = model.predict(X_new)
|
|
54
86
|
```
|
|
55
87
|
|
|
@@ -63,14 +95,14 @@ teacher = op.model.Predictor(
|
|
|
63
95
|
)
|
|
64
96
|
|
|
65
97
|
# Distill into a reasoning model
|
|
66
|
-
rm = op.reasoning.fit(dataset, teacher=teacher)
|
|
98
|
+
rm = op.reasoning.fit(dataset, teacher=teacher).wait()
|
|
67
99
|
```
|
|
68
100
|
|
|
69
101
|
You can also distill an existing OuterProduct-trained model:
|
|
70
102
|
|
|
71
103
|
```python
|
|
72
|
-
trained = op.Trainer.configure(dataset).run()
|
|
73
|
-
rm = op.reasoning.fit(dataset, teacher=trained)
|
|
104
|
+
trained = op.Trainer.configure(dataset).run().wait()
|
|
105
|
+
rm = op.reasoning.fit(dataset, teacher=trained).wait()
|
|
74
106
|
```
|
|
75
107
|
|
|
76
108
|
### Reasoning Model Operations
|
|
@@ -90,16 +122,23 @@ trace = rm.scenario(X, target_class=1, max_steps=10)
|
|
|
90
122
|
|
|
91
123
|
# Pattern aggregation across rejected predictions
|
|
92
124
|
import outerproduct as op
|
|
93
|
-
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()
|
|
94
126
|
pt.distribution(X_new)
|
|
95
127
|
```
|
|
96
128
|
|
|
97
|
-
###
|
|
129
|
+
### Jobs
|
|
98
130
|
|
|
99
|
-
|
|
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:
|
|
100
135
|
|
|
101
136
|
```python
|
|
102
|
-
|
|
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
|
|
103
142
|
```
|
|
104
143
|
|
|
105
144
|
### Configuration
|
|
@@ -114,7 +153,5 @@ op.init()
|
|
|
114
153
|
op.init(
|
|
115
154
|
api_key="op_live_...",
|
|
116
155
|
base_url="https://api.outerproduct.com",
|
|
117
|
-
hardware=op.ModalHardware(),
|
|
118
156
|
)
|
|
119
157
|
```
|
|
120
|
-
|
|
@@ -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 }
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""OuterProduct — a Python SDK for training and explaining tabular ML models.
|
|
2
|
+
|
|
3
|
+
The package exposes a small, flat surface organised around the following
|
|
4
|
+
core concepts:
|
|
5
|
+
|
|
6
|
+
- :class:`~outerproduct.dataset.Dataset` — a tabular dataset with an optional label column and
|
|
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.
|
|
11
|
+
- :class:`~outerproduct.trainer.Trainer` — orchestrates general-purpose training and
|
|
12
|
+
returns a :class:`~outerproduct.model.Model` via :meth:`~outerproduct.trainer.Trainer.configure` and
|
|
13
|
+
:meth:`~outerproduct.trainer.Trainer.run`.
|
|
14
|
+
- :class:`~outerproduct.model.Model` — a trained predictor with :meth:`~outerproduct.model.Model.predict`.
|
|
15
|
+
- :class:`~outerproduct.model.Predictor` — a duck-typed wrapper around an external
|
|
16
|
+
prediction endpoint, used to hand black-box models into
|
|
17
|
+
:func:`~outerproduct.reasoning.fit` for distillation.
|
|
18
|
+
- :class:`~outerproduct.model.ReasoningModel` — a :class:`~outerproduct.model.Model` that also supports
|
|
19
|
+
:meth:`~outerproduct.model.ReasoningModel.explain`, :meth:`~outerproduct.model.ReasoningModel.get_global_drivers`,
|
|
20
|
+
and :meth:`~outerproduct.model.ReasoningModel.scenario`.
|
|
21
|
+
Produced by :func:`~outerproduct.reasoning.fit`.
|
|
22
|
+
- :class:`~outerproduct.reasoning.pattern_tracker.PatternTracker` —
|
|
23
|
+
aggregates the per-row local-rule explanations a
|
|
24
|
+
:class:`~outerproduct.model.ReasoningModel` produces over a target
|
|
25
|
+
prediction band into a small set of executable filter patterns.
|
|
26
|
+
- :class:`~outerproduct.reasoning.Reasoning` / :class:`~outerproduct.reasoning.ScenarioResult` — the typed results of
|
|
27
|
+
reasoning calls.
|
|
28
|
+
|
|
29
|
+
Quickstart
|
|
30
|
+
----------
|
|
31
|
+
.. code-block:: python
|
|
32
|
+
|
|
33
|
+
import outerproduct as op
|
|
34
|
+
|
|
35
|
+
op.init(api_key="...")
|
|
36
|
+
dataset = op.LocalDataset.from_csv("customers.csv").upload()
|
|
37
|
+
reasoning_model = op.reasoning.fit(dataset, task=op.BinclassConfig(label_column="churn"))
|
|
38
|
+
test_dataset = op.Dataset(X_new)
|
|
39
|
+
predictions = reasoning_model.predict(test_dataset)
|
|
40
|
+
reasoning = reasoning_model.explain(test_dataset)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from contextlib import contextmanager
|
|
46
|
+
from contextvars import ContextVar
|
|
47
|
+
from dataclasses import dataclass, field
|
|
48
|
+
from importlib.metadata import version as _pkg_version
|
|
49
|
+
from typing import TYPE_CHECKING, Generator
|
|
50
|
+
|
|
51
|
+
from outerproduct_http_types import (
|
|
52
|
+
BinclassConfig,
|
|
53
|
+
ConnectorType,
|
|
54
|
+
ForecastingConfig,
|
|
55
|
+
MulticlassConfig,
|
|
56
|
+
RegressionConfig,
|
|
57
|
+
SequenceConfig,
|
|
58
|
+
TaskConfig,
|
|
59
|
+
TaskKind,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
from outerproduct import agentic
|
|
63
|
+
from outerproduct.connector import (
|
|
64
|
+
Connector,
|
|
65
|
+
DatabricksConnector,
|
|
66
|
+
S3Connector,
|
|
67
|
+
SnowflakeConnector,
|
|
68
|
+
)
|
|
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
|
+
)
|
|
84
|
+
from outerproduct import reasoning
|
|
85
|
+
from outerproduct.reasoning import (
|
|
86
|
+
Change,
|
|
87
|
+
Counterfactual,
|
|
88
|
+
QueryResult,
|
|
89
|
+
Reasoning,
|
|
90
|
+
ScenarioResult,
|
|
91
|
+
)
|
|
92
|
+
from outerproduct.reasoning import pattern_tracker # noqa: F401 — makes op.reasoning.pattern_tracker accessible
|
|
93
|
+
from outerproduct.trainer import (
|
|
94
|
+
Metric,
|
|
95
|
+
Trainer,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if TYPE_CHECKING:
|
|
99
|
+
from outerproduct.client import AsyncOuterProductClient, OuterProductClient
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
__version__ = _pkg_version("outerproduct")
|
|
103
|
+
except Exception:
|
|
104
|
+
__version__ = "unknown"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class _GlobalState:
|
|
109
|
+
"""Module-level state populated by :func:`~outerproduct.init`. Internal."""
|
|
110
|
+
|
|
111
|
+
api_key: str | None = None
|
|
112
|
+
base_url: str | None = None
|
|
113
|
+
extra: dict[str, object] = field(default_factory=dict)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_state = _GlobalState()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------
|
|
120
|
+
# Per-request client override (contextvar)
|
|
121
|
+
# ---------------------------------------------------------------------
|
|
122
|
+
#
|
|
123
|
+
# By default, every SDK HTTP call builds an :class:`OuterProductClient`
|
|
124
|
+
# from ``_state`` — one shared bearer token for the whole process. That
|
|
125
|
+
# works for single-user SDK consumers (notebooks, scripts).
|
|
126
|
+
#
|
|
127
|
+
# Multi-tenant SDK consumers (e.g. an MCP server serving many users)
|
|
128
|
+
# need *per-request* credentials. The contextvar below lets a caller
|
|
129
|
+
# enter a scope in which SDK HTTP helpers use a provided pre-built
|
|
130
|
+
# client instead of the global state — without mutating the global
|
|
131
|
+
# state and without changing every SDK class signature.
|
|
132
|
+
|
|
133
|
+
_request_client: ContextVar["OuterProductClient | None"] = ContextVar(
|
|
134
|
+
"outerproduct_request_client", default=None
|
|
135
|
+
)
|
|
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
|
+
|
|
145
|
+
|
|
146
|
+
@contextmanager
|
|
147
|
+
def use_client(
|
|
148
|
+
client: "OuterProductClient",
|
|
149
|
+
) -> Generator[None, None, None]:
|
|
150
|
+
"""Use ``client`` for every SDK HTTP call inside this block.
|
|
151
|
+
|
|
152
|
+
Inside the ``with`` block, the SDK's internal client helper yields
|
|
153
|
+
``client`` rather than building one from ``op._state``. The caller
|
|
154
|
+
owns ``client``'s lifecycle — the SDK will not close it.
|
|
155
|
+
|
|
156
|
+
Designed for service-style consumers (e.g. an MCP server) that need
|
|
157
|
+
to thread per-request credentials through SDK calls without mutating
|
|
158
|
+
global state. Contextvars propagate across ``asyncio.to_thread`` and
|
|
159
|
+
``asyncio.TaskGroup``, so this works inside async dispatch.
|
|
160
|
+
|
|
161
|
+
Examples
|
|
162
|
+
--------
|
|
163
|
+
.. code-block:: python
|
|
164
|
+
|
|
165
|
+
from outerproduct.client import OuterProductClient
|
|
166
|
+
import outerproduct as op
|
|
167
|
+
|
|
168
|
+
# Mint a per-request bearer (e.g. a short-lived JWT) and use it
|
|
169
|
+
# for every SDK call inside the `with` block.
|
|
170
|
+
with OuterProductClient.from_credentials(base_url, per_request_token) as client:
|
|
171
|
+
with op.use_client(client):
|
|
172
|
+
model = op.ReasoningModel(id="mdl-1", feature_names=["a","b"])
|
|
173
|
+
preds = model.predict(X)
|
|
174
|
+
"""
|
|
175
|
+
token = _request_client.set(client)
|
|
176
|
+
try:
|
|
177
|
+
yield
|
|
178
|
+
finally:
|
|
179
|
+
_request_client.reset(token)
|
|
180
|
+
|
|
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
|
+
|
|
208
|
+
def init(
|
|
209
|
+
api_key: str | None = None,
|
|
210
|
+
*,
|
|
211
|
+
base_url: str | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Configure the OuterProduct SDK for the current Python process.
|
|
214
|
+
|
|
215
|
+
Call once near the top of your program. Subsequent operations
|
|
216
|
+
(training, prediction, uploads) read credentials and the API base URL
|
|
217
|
+
from the state recorded here.
|
|
218
|
+
|
|
219
|
+
Examples
|
|
220
|
+
--------
|
|
221
|
+
.. code-block:: python
|
|
222
|
+
|
|
223
|
+
import outerproduct as op
|
|
224
|
+
op.init(api_key="op_live_...")
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
api_key : str, optional
|
|
229
|
+
Bearer token used to authenticate against the OuterProduct API.
|
|
230
|
+
If omitted, the ``OUTERPRODUCT_API_KEY`` environment variable is
|
|
231
|
+
consulted at use time.
|
|
232
|
+
base_url : str, optional
|
|
233
|
+
Override for the API base URL. If omitted, the SDK uses the
|
|
234
|
+
production endpoint.
|
|
235
|
+
"""
|
|
236
|
+
_state.api_key = api_key
|
|
237
|
+
_state.base_url = base_url
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_datasets() -> list[Dataset]:
|
|
241
|
+
"""List datasets owned by the configured caller.
|
|
242
|
+
|
|
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
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_dataset(dataset_id: str) -> Dataset:
|
|
260
|
+
"""Fetch a single dataset by id; useful for cross-session reuse.
|
|
261
|
+
|
|
262
|
+
The returned Dataset has its ``_id`` and ``connection_config`` already
|
|
263
|
+
stamped, so subsequent fit/predict calls short-circuit the server-side
|
|
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.
|
|
287
|
+
"""
|
|
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
|
|
301
|
+
|
|
302
|
+
with _client() as client:
|
|
303
|
+
record = client.models_api.get(model_id)
|
|
304
|
+
return Model._from_response(record)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
__all__ = [
|
|
308
|
+
# Bootstrap
|
|
309
|
+
"init",
|
|
310
|
+
"use_client",
|
|
311
|
+
"use_aclient",
|
|
312
|
+
"get_dataset",
|
|
313
|
+
"get_datasets",
|
|
314
|
+
"get_model",
|
|
315
|
+
"get_models",
|
|
316
|
+
"__version__",
|
|
317
|
+
# Dataset
|
|
318
|
+
"Dataset",
|
|
319
|
+
"LocalDataset",
|
|
320
|
+
"DatasetColumn",
|
|
321
|
+
"Datatype",
|
|
322
|
+
"col",
|
|
323
|
+
"Expr",
|
|
324
|
+
# Jobs
|
|
325
|
+
"Job",
|
|
326
|
+
"JobFailed",
|
|
327
|
+
"JobNotReady",
|
|
328
|
+
"JobResult",
|
|
329
|
+
"ReasoningFitJob",
|
|
330
|
+
"TrainerJob",
|
|
331
|
+
# Model
|
|
332
|
+
"Model",
|
|
333
|
+
"DiffModel",
|
|
334
|
+
"ReasoningModel",
|
|
335
|
+
"SequenceReasoningModel",
|
|
336
|
+
# Reasoning (submodule, accessed as op.reasoning.*)
|
|
337
|
+
"reasoning",
|
|
338
|
+
"Reasoning",
|
|
339
|
+
"ScenarioResult",
|
|
340
|
+
"QueryResult",
|
|
341
|
+
"Counterfactual",
|
|
342
|
+
"Change",
|
|
343
|
+
# Trainer
|
|
344
|
+
"Trainer",
|
|
345
|
+
"Metric",
|
|
346
|
+
# Task config
|
|
347
|
+
"TaskConfig",
|
|
348
|
+
"TaskKind",
|
|
349
|
+
"RegressionConfig",
|
|
350
|
+
"BinclassConfig",
|
|
351
|
+
"MulticlassConfig",
|
|
352
|
+
"ForecastingConfig",
|
|
353
|
+
"SequenceConfig",
|
|
354
|
+
# Connector
|
|
355
|
+
"Connector",
|
|
356
|
+
"ConnectorType",
|
|
357
|
+
"S3Connector",
|
|
358
|
+
"SnowflakeConnector",
|
|
359
|
+
"DatabricksConnector",
|
|
360
|
+
# Agentic feature sourcing (submodule, accessed as op.agentic.*)
|
|
361
|
+
"agentic",
|
|
362
|
+
]
|