Typed Python client for the MeasyAI API — OpenAI-compatible chat completions, streaming, batches and webhook verification.
Project description
MeasyAI Python SDK
Python client for the MeasyAI API.
pip install measyai
Requires Python 3.9+. One dependency: httpx.
Chat
from measyai import MeasyAI
client = MeasyAI() # reads MEASYAI_API_KEY
completion = client.chat.create(
model="measyai/meridian",
messages=[{"role": "user", "content": "Explain HTTP/3 briefly."}],
)
print(completion.text)
completion.text is the first choice's content. The full shape is there
when you need it — completion.choices[0].finish_reason,
completion.usage.total_tokens.
Streaming
stream() opens the connection before it returns, so a rejected request
raises there rather than on the first frame. Iterating yields chunks;
text() yields just the deltas.
with client.chat.stream(
messages=[{"role": "user", "content": "Why is Postgres MVCC useful?"}],
) as stream:
for delta in stream.text():
print(delta, end="", flush=True)
Use it as a context manager. Iterating to the end releases the connection
on its own, but breaking out early only does so once the iterator is
collected — with makes that immediate.
For finish reasons or the closing usage block, iterate the chunks:
with client.chat.stream(messages=messages) as stream:
for chunk in stream:
if chunk.usage:
print(f"\n{chunk.usage.total_tokens} tokens")
Async
AsyncMeasyAI has the same surface with await on the calls and
async for on the streams.
import asyncio
from measyai import AsyncMeasyAI
async def main() -> None:
async with AsyncMeasyAI() as client:
stream = await client.chat.stream(
messages=[{"role": "user", "content": "Explain QUIC."}],
)
async with stream:
async for delta in stream.text():
print(delta, end="", flush=True)
asyncio.run(main())
Unmodelled parameters
Anything this package does not model is passed as a keyword argument and forwarded to the provider unchanged:
client.chat.create(
messages=messages,
temperature=0.2,
tools=my_tools,
response_format={"type": "json_object"},
)
model, messages and stream are owned by the method signature and are
refused rather than silently dropped — otherwise stream=True would hand
create() an SSE body it then tries to decode as JSON.
Response fields this package does not model are kept too: every object
carries the payload it was built from in .raw.
Batches
from measyai import BatchRequest
batch = client.batches.create([
BatchRequest(custom_id="row-1", body={"messages": messages}),
])
done = client.batches.wait(batch.id, interval=5)
print(f"{done.completed} completed, {done.failed} failed")
for item in done.items:
if item.response:
print(item.custom_id, item.response.text)
wait() polls until the batch is terminal. It waits indefinitely by
default — a large batch legitimately runs for hours — so pass timeout=
if you want it to give up.
Webhooks
Verify against the raw body. Re-encoding a payload you already decoded will not reproduce the same bytes, and the check will fail on a delivery that was perfectly valid.
import measyai
@app.post("/webhooks/measyai")
async def receive(request: Request):
raw = await request.body()
if not measyai.verify_webhook(
secret,
request.headers[measyai.HEADER_SIGNATURE],
request.headers[measyai.HEADER_TIMESTAMP],
raw,
):
raise HTTPException(400)
...
The timestamp is signed inside the message and checked against a tolerance, so a captured delivery cannot be replayed. The comparison is constant-time.
Errors
Everything raised inherits from MeasyAIError. Below it the tree forks on
why the call failed: APIError when the API rejected the request,
APIConnectionError when there was never a response.
from measyai import APIError, MeasyAIError
try:
completion = client.chat.create(messages=messages)
except APIError as err:
if err.retryable: # 429 or 5xx — worth backing off
...
print(err.code, err.request_id)
except MeasyAIError:
... # connection, timeout, malformed body
APIError also subclasses by status — AuthenticationError,
RateLimitError, NotFoundError and the rest — so you can catch just the
case you handle. Branch on err.code, which is stable; err.message is
written for humans and may be reworded.
Nothing is retried for you. retryable tells you when a retry could
succeed; the backoff policy is yours.
Configuration
client = MeasyAI(
api_key="msy_...", # or MEASYAI_API_KEY
base_url="http://localhost:8080", # or MEASYAI_BASE_URL
user_agent="acme-crm/3.1", # appended, for server logs
timeout=httpx.Timeout(connect=10.0, read=60.0),
)
The default timeouts bound stalls, not total duration — a streamed generation legitimately runs for minutes, and an overall deadline would cut it off mid-answer.
Pass http_client= to supply your own httpx.Client for a proxy, a
custom transport or a test double. You keep ownership of it: closing the
MeasyAI client leaves yours open.
Reuse one client for the life of the process. It is safe to share between threads, and a client per call throws away the connection pool.
Using an OpenAI client instead
The /v1 surface is OpenAI-compatible, so the official OpenAI SDK works
by changing its base URL:
from openai import OpenAI
client = OpenAI(api_key="msy_...", base_url="https://api.measyai.com/v1")
Use this package when you also want batches and webhook verification typed.
Links
MIT licensed.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file measyai-2.0.0.tar.gz.
File metadata
- Download URL: measyai-2.0.0.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
891ce654c5e4b406e96c45c63526e58ab87ef5ebafa89ab0474c381e5e9c27ae
|
|
| MD5 |
4850117ae3d44563250d444a8be04f13
|
|
| BLAKE2b-256 |
12b12c1b36abe6c51b981f9294d54d00819e483af062fb94ba5b231bbd1ef80a
|
Provenance
The following attestation bundles were made for measyai-2.0.0.tar.gz:
Publisher:
ci.yml on measyai/Python-SDK
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
measyai-2.0.0.tar.gz -
Subject digest:
891ce654c5e4b406e96c45c63526e58ab87ef5ebafa89ab0474c381e5e9c27ae - Sigstore transparency entry: 2255864301
- Sigstore integration time:
-
Permalink:
measyai/Python-SDK@e371b09e1ce2bfdb2ad64fb1a2336af423a46239 -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/measyai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@e371b09e1ce2bfdb2ad64fb1a2336af423a46239 -
Trigger Event:
release
-
Statement type:
File details
Details for the file measyai-2.0.0-py3-none-any.whl.
File metadata
- Download URL: measyai-2.0.0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2bc7dadb850c63036866244d62c34b3a27e9477761330efc5983c2a4fa05f5f
|
|
| MD5 |
ca87cc2ade7e88213d69024059fcd418
|
|
| BLAKE2b-256 |
c55489f3c973c7a8c2b3179b4c43d51fcd209331b3359d6ac14012e07f3dc728
|
Provenance
The following attestation bundles were made for measyai-2.0.0-py3-none-any.whl:
Publisher:
ci.yml on measyai/Python-SDK
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
measyai-2.0.0-py3-none-any.whl -
Subject digest:
b2bc7dadb850c63036866244d62c34b3a27e9477761330efc5983c2a4fa05f5f - Sigstore transparency entry: 2255864315
- Sigstore integration time:
-
Permalink:
measyai/Python-SDK@e371b09e1ce2bfdb2ad64fb1a2336af423a46239 -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/measyai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@e371b09e1ce2bfdb2ad64fb1a2336af423a46239 -
Trigger Event:
release
-
Statement type: