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.
@@ -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"