factorial-compute 0.0.1__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.
- factorial_compute-0.0.1/PKG-INFO +92 -0
- factorial_compute-0.0.1/README.md +78 -0
- factorial_compute-0.0.1/factorial_compute/__init__.py +78 -0
- factorial_compute-0.0.1/factorial_compute/_branding.py +39 -0
- factorial_compute-0.0.1/factorial_compute/client.py +639 -0
- factorial_compute-0.0.1/factorial_compute/errors.py +140 -0
- factorial_compute-0.0.1/factorial_compute/outputs.py +151 -0
- factorial_compute-0.0.1/factorial_compute/types.py +505 -0
- factorial_compute-0.0.1/factorial_compute.egg-info/PKG-INFO +92 -0
- factorial_compute-0.0.1/factorial_compute.egg-info/SOURCES.txt +13 -0
- factorial_compute-0.0.1/factorial_compute.egg-info/dependency_links.txt +1 -0
- factorial_compute-0.0.1/factorial_compute.egg-info/requires.txt +1 -0
- factorial_compute-0.0.1/factorial_compute.egg-info/top_level.txt +1 -0
- factorial_compute-0.0.1/pyproject.toml +29 -0
- factorial_compute-0.0.1/setup.cfg +4 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: factorial-compute
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Typed answers from AI compute: images, masks, transcripts and text, whichever model or supplier produced them.
|
|
5
|
+
Project-URL: Homepage, https://factorialcompute.com
|
|
6
|
+
Project-URL: Repository, https://github.com/prateekvjoshi/factorial-compute
|
|
7
|
+
Keywords: ai,inference,api,segmentation,transcription,image-generation
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: httpx>=0.27
|
|
14
|
+
|
|
15
|
+
# factorial-compute
|
|
16
|
+
|
|
17
|
+
One API for AI compute that returns **typed answers** — an image, a set of
|
|
18
|
+
masks, a transcript, text — in the same shape whichever model or supplier
|
|
19
|
+
produced it.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install factorial-compute
|
|
23
|
+
export FACTORIAL_API_KEY=fc_...
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from factorial_compute import Client
|
|
28
|
+
|
|
29
|
+
f = Client() # reads FACTORIAL_API_KEY
|
|
30
|
+
|
|
31
|
+
# Text, and JSON on request.
|
|
32
|
+
print(f.run(model="gpt-oss-120b", input="Name three uses for a forklift.").text)
|
|
33
|
+
|
|
34
|
+
# An image from a prompt.
|
|
35
|
+
result = f.run(model="flux-schnell", input={"prompt": "a red forklift in a warehouse"})
|
|
36
|
+
result.images[0].save("forklift.jpg")
|
|
37
|
+
|
|
38
|
+
# Masks: one per object, each with a box measured from the mask.
|
|
39
|
+
result = f.run(model="sam2-segment", input={
|
|
40
|
+
"image": "warehouse.jpg", # a local path: uploaded for you
|
|
41
|
+
"objects": [{"id": "pallet", "box": [40, 60, 300, 280]},
|
|
42
|
+
{"id": "cone", "point": [512, 240]}],
|
|
43
|
+
})
|
|
44
|
+
for mask in result.masks:
|
|
45
|
+
print(mask.id, mask.box)
|
|
46
|
+
mask.save(f"{mask.id}.png")
|
|
47
|
+
|
|
48
|
+
# A transcript, with timestamps.
|
|
49
|
+
result = f.run(model="whisper", input={"audio": "meeting.mp3"})
|
|
50
|
+
print(result.transcript.text)
|
|
51
|
+
for segment in result.transcript.segments:
|
|
52
|
+
print(f"{segment.start:6.1f}s {segment.text}")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That is the whole surface for most work: `run()` with a model id and an
|
|
56
|
+
`input`, then read the typed answer.
|
|
57
|
+
|
|
58
|
+
## Rules worth knowing
|
|
59
|
+
|
|
60
|
+
- **Files go in by path, URL or bytes** under the keys `image`, `images`,
|
|
61
|
+
`audio` and `video`. The client uploads them and sends a reference. (A chat
|
|
62
|
+
model that reads images takes them as URLs, sent as written.)
|
|
63
|
+
- **Answers are typed.** `.text`, `.images`, `.masks`, `.transcript`. Asking a
|
|
64
|
+
result for the wrong kind raises, rather than returning something empty.
|
|
65
|
+
- **Files come back as handles.** `.save(path)` writes one; `.read()` returns
|
|
66
|
+
the bytes. They stay on the server until you ask.
|
|
67
|
+
- **Retries are safe.** Every `run()` carries an idempotency key and is retried
|
|
68
|
+
on connection failures and on 429/502/503/504, so a retry never runs — or
|
|
69
|
+
bills — the work twice.
|
|
70
|
+
- **Errors say what to change.** A refused input raises `InvalidInput` whose
|
|
71
|
+
message names the field and a value that works.
|
|
72
|
+
- **Usage is in the model's own unit**: tokens, `images`, `audio_seconds`.
|
|
73
|
+
`result.usage.units` has it.
|
|
74
|
+
|
|
75
|
+
## Which models exist
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
for model in f.models.catalog():
|
|
79
|
+
print(model["id"], model.get("output"), model["unit"])
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`output` says what kind of answer a model returns — `image`, `masks`,
|
|
83
|
+
`transcript` — so code can pick a model by what it produces.
|
|
84
|
+
|
|
85
|
+
## Longer work
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
handle = f.submit(model="flux-schnell", input={"prompt": "..."})
|
|
89
|
+
result = handle.wait()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`submit` returns before the work runs and survives your process exiting.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# factorial-compute
|
|
2
|
+
|
|
3
|
+
One API for AI compute that returns **typed answers** — an image, a set of
|
|
4
|
+
masks, a transcript, text — in the same shape whichever model or supplier
|
|
5
|
+
produced it.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install factorial-compute
|
|
9
|
+
export FACTORIAL_API_KEY=fc_...
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from factorial_compute import Client
|
|
14
|
+
|
|
15
|
+
f = Client() # reads FACTORIAL_API_KEY
|
|
16
|
+
|
|
17
|
+
# Text, and JSON on request.
|
|
18
|
+
print(f.run(model="gpt-oss-120b", input="Name three uses for a forklift.").text)
|
|
19
|
+
|
|
20
|
+
# An image from a prompt.
|
|
21
|
+
result = f.run(model="flux-schnell", input={"prompt": "a red forklift in a warehouse"})
|
|
22
|
+
result.images[0].save("forklift.jpg")
|
|
23
|
+
|
|
24
|
+
# Masks: one per object, each with a box measured from the mask.
|
|
25
|
+
result = f.run(model="sam2-segment", input={
|
|
26
|
+
"image": "warehouse.jpg", # a local path: uploaded for you
|
|
27
|
+
"objects": [{"id": "pallet", "box": [40, 60, 300, 280]},
|
|
28
|
+
{"id": "cone", "point": [512, 240]}],
|
|
29
|
+
})
|
|
30
|
+
for mask in result.masks:
|
|
31
|
+
print(mask.id, mask.box)
|
|
32
|
+
mask.save(f"{mask.id}.png")
|
|
33
|
+
|
|
34
|
+
# A transcript, with timestamps.
|
|
35
|
+
result = f.run(model="whisper", input={"audio": "meeting.mp3"})
|
|
36
|
+
print(result.transcript.text)
|
|
37
|
+
for segment in result.transcript.segments:
|
|
38
|
+
print(f"{segment.start:6.1f}s {segment.text}")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That is the whole surface for most work: `run()` with a model id and an
|
|
42
|
+
`input`, then read the typed answer.
|
|
43
|
+
|
|
44
|
+
## Rules worth knowing
|
|
45
|
+
|
|
46
|
+
- **Files go in by path, URL or bytes** under the keys `image`, `images`,
|
|
47
|
+
`audio` and `video`. The client uploads them and sends a reference. (A chat
|
|
48
|
+
model that reads images takes them as URLs, sent as written.)
|
|
49
|
+
- **Answers are typed.** `.text`, `.images`, `.masks`, `.transcript`. Asking a
|
|
50
|
+
result for the wrong kind raises, rather than returning something empty.
|
|
51
|
+
- **Files come back as handles.** `.save(path)` writes one; `.read()` returns
|
|
52
|
+
the bytes. They stay on the server until you ask.
|
|
53
|
+
- **Retries are safe.** Every `run()` carries an idempotency key and is retried
|
|
54
|
+
on connection failures and on 429/502/503/504, so a retry never runs — or
|
|
55
|
+
bills — the work twice.
|
|
56
|
+
- **Errors say what to change.** A refused input raises `InvalidInput` whose
|
|
57
|
+
message names the field and a value that works.
|
|
58
|
+
- **Usage is in the model's own unit**: tokens, `images`, `audio_seconds`.
|
|
59
|
+
`result.usage.units` has it.
|
|
60
|
+
|
|
61
|
+
## Which models exist
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
for model in f.models.catalog():
|
|
65
|
+
print(model["id"], model.get("output"), model["unit"])
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`output` says what kind of answer a model returns — `image`, `masks`,
|
|
69
|
+
`transcript` — so code can pick a model by what it produces.
|
|
70
|
+
|
|
71
|
+
## Longer work
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
handle = f.submit(model="flux-schnell", input={"prompt": "..."})
|
|
75
|
+
result = handle.wait()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`submit` returns before the work runs and survives your process exiting.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""The Python client.
|
|
2
|
+
|
|
3
|
+
from factorial_compute import Client
|
|
4
|
+
|
|
5
|
+
f = Client()
|
|
6
|
+
print(f.run(model="assistant", input="Explain Euler's identity.").text)
|
|
7
|
+
|
|
8
|
+
The package name and every customer-visible string live in `_branding.py`, so
|
|
9
|
+
renaming the product is one file rather than a search across a client people
|
|
10
|
+
have already installed.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from ._branding import ENV_API_KEY, ENV_BASE_URL, PRODUCT
|
|
14
|
+
from .client import Client
|
|
15
|
+
from .errors import (
|
|
16
|
+
AccountSuspended,
|
|
17
|
+
AuthenticationError,
|
|
18
|
+
BudgetExceeded,
|
|
19
|
+
CapacityUnavailable,
|
|
20
|
+
ExecutionFailed,
|
|
21
|
+
FactorialError,
|
|
22
|
+
InvalidInput,
|
|
23
|
+
ModelNotSupported,
|
|
24
|
+
NotDeployed,
|
|
25
|
+
NotFound,
|
|
26
|
+
PermissionDenied,
|
|
27
|
+
RateLimited,
|
|
28
|
+
Timeout,
|
|
29
|
+
)
|
|
30
|
+
from .outputs import Artifact, Image, Mask, Masks, Segment, Transcript
|
|
31
|
+
from .types import (
|
|
32
|
+
Batch,
|
|
33
|
+
Event,
|
|
34
|
+
Execution,
|
|
35
|
+
ModelInfo,
|
|
36
|
+
Result,
|
|
37
|
+
Session,
|
|
38
|
+
Timing,
|
|
39
|
+
Usage,
|
|
40
|
+
UsageReport,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__version__ = "0.0.1"
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"ENV_API_KEY",
|
|
47
|
+
"ENV_BASE_URL",
|
|
48
|
+
"PRODUCT",
|
|
49
|
+
"AccountSuspended",
|
|
50
|
+
"Artifact",
|
|
51
|
+
"AuthenticationError",
|
|
52
|
+
"Batch",
|
|
53
|
+
"BudgetExceeded",
|
|
54
|
+
"CapacityUnavailable",
|
|
55
|
+
"Client",
|
|
56
|
+
"Event",
|
|
57
|
+
"Execution",
|
|
58
|
+
"ExecutionFailed",
|
|
59
|
+
"FactorialError",
|
|
60
|
+
"Image",
|
|
61
|
+
"InvalidInput",
|
|
62
|
+
"Mask",
|
|
63
|
+
"Masks",
|
|
64
|
+
"ModelInfo",
|
|
65
|
+
"ModelNotSupported",
|
|
66
|
+
"NotDeployed",
|
|
67
|
+
"NotFound",
|
|
68
|
+
"PermissionDenied",
|
|
69
|
+
"RateLimited",
|
|
70
|
+
"Result",
|
|
71
|
+
"Segment",
|
|
72
|
+
"Session",
|
|
73
|
+
"Timeout",
|
|
74
|
+
"Timing",
|
|
75
|
+
"Transcript",
|
|
76
|
+
"Usage",
|
|
77
|
+
"UsageReport",
|
|
78
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Everything the product's name touches, in one place.
|
|
2
|
+
|
|
3
|
+
The name has changed once and could change again, and every string below moves
|
|
4
|
+
with it: the environment variable a customer exports, the prefix on their key,
|
|
5
|
+
the header they set, and the sentence in every error. Collecting them here
|
|
6
|
+
makes that a one-file change rather than a search across a client people have
|
|
7
|
+
already installed.
|
|
8
|
+
|
|
9
|
+
These deliberately duplicate constants that also exist on the server, because
|
|
10
|
+
the client is its own distribution and must not import the platform to send a
|
|
11
|
+
request. Duplication across that boundary is the price of the boundary; what
|
|
12
|
+
makes it safe is that `tests/test_structural_invariants.py` asserts the two
|
|
13
|
+
sides agree. A key prefix that matched on one side and not the other would
|
|
14
|
+
reject every key with a message about the key being malformed.
|
|
15
|
+
|
|
16
|
+
`KEY_PREFIXES` is a tuple rather than a string on purpose. A rename can issue
|
|
17
|
+
new keys under a new prefix while the ones already sitting in customers'
|
|
18
|
+
`.env` files keep working - which matters most for the earliest users, who are
|
|
19
|
+
the ones you least want to send a "please rotate your key" email to. It holds
|
|
20
|
+
one entry today because no key was ever issued under the old name.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
# What the product is called in a sentence: "cannot reach Factorial at ...".
|
|
26
|
+
PRODUCT = "Factorial"
|
|
27
|
+
ENV_API_KEY = "FACTORIAL_API_KEY"
|
|
28
|
+
ENV_BASE_URL = "FACTORIAL_BASE_URL"
|
|
29
|
+
KEY_PREFIXES = ("fc_",)
|
|
30
|
+
TAGS_HEADER = "X-Factorial-Tags"
|
|
31
|
+
# Production, because this package is what a customer pip-installs and they
|
|
32
|
+
# have no localhost to talk to. Anyone running the server themselves sets
|
|
33
|
+
# FACTORIAL_BASE_URL, and the connection error names the URL it tried so a
|
|
34
|
+
# forgotten override is one line to diagnose rather than a mystery.
|
|
35
|
+
DEFAULT_BASE_URL = "https://api.factorialcompute.com"
|
|
36
|
+
# Lowercase and hyphenated, which is the convention for a User-Agent token and
|
|
37
|
+
# is not the same string as PRODUCT above - hence spelled out rather than
|
|
38
|
+
# interpolated from it.
|
|
39
|
+
USER_AGENT = "factorial-compute-python/0.0.1"
|