Metadata-Version: 2.5
Name: spt-models
Version: 0.4.1
Summary: Python client for the SPT Models GPU inference platform
Project-URL: Homepage, https://sponge-theory.ai
Project-URL: Repository, https://github.com/sponge-theory/spt-models
Project-URL: Documentation, https://github.com/sponge-theory/spt-models/tree/main/spt-models-python
Author-email: Sponge Theory <contact@sponge-theory.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: ai,api-client,gpu,inference,llm,ml,openai
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-httpx>=0.34; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.22; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# spt-models

Python client for the SPT Models GPU inference platform.

## Thinking models and streamed tool calls

Responses keep every field the gateway sends. `message.reasoning_content` and
`usage.reasoning_tokens` carry a thinking model's reasoning; models that
preserve thinking across turns expect `reasoning_content` back in the assistant
message of the history. A stream delivers tool calls as OpenAI fragments on
`delta.tool_calls`, next to `delta.content` and `delta.reasoning_content`:

```python
calls: dict[int, dict] = {}
for chunk in client.chat.completions.create(model="default-vlm", messages=messages,
                                            tools=tools, stream=True):
    for fragment in chunk.choices[0].delta.tool_calls or []:
        call = calls.setdefault(fragment["index"], {"name": "", "arguments": ""})
        call["name"] += fragment.get("function", {}).get("name") or ""
        call["arguments"] += fragment.get("function", {}).get("arguments") or ""
```

A field the gateway adds later stays reachable through `model_extra`.

## Speech

`client.audio.speech.create()` returns the audio bytes. Since 0.4.1 the JSON
envelope the gateway sends for a non-streamed answer is decoded, as
`client.music.generate()` already did; streamed audio and SSE events come back
unchanged.

```python
wav = client.audio.speech.create(model="default-tts", input="Bonjour", response_format="wav")
pathlib.Path("bonjour.wav").write_bytes(wav)
```

## Model cards

`client.models.retrieve(name)` returns the model card: `type`, `description`,
`capabilities` and `prompting_guide`, whose `recommended_params` are the
settings to apply before inferring. `client.models.list(verbose=True)` returns
the same card for every model; `list()` without the flag is unchanged.

```python
card = client.models.retrieve("default-image-model")
params = (card.prompting_guide or {}).get("recommended_params", {})
```

## Music transcription

`client.music.transcribe()` turns a song recording into a score (ABC, keys,
sections, optional MIDI). Only the options you pass are sent; `wait=True` runs
the transcription as a job and polls it until it ends.

```python
score = client.music.transcribe(model="sheetsage2", file="song.wav",
                                task="melody_full", include_midi=True, wait=True)
print(score.abc or score.abc_error)
```

`await client.music.atranscribe(...)` is the async counterpart.

## Model aliases

An admin can publish client-facing names (`gpt-4`, `prod-embeddings`, …) that
point at a catalogue model. Every `/v1/*` call accepts an alias wherever it
accepts a slug, and echoes the alias back in the response `model` field.
`client.admin.aliases` manages them (admin token required); `Model.alias_of`
tells an alias entry apart from a real model in `client.models.list()`.

```python
from spt_models import Client

with Client(api_key="sk-...", admin_token="...") as client:
    # Create an alias pointing at model id 1, then use it like a slug.
    alias = client.admin.aliases.create("gpt-4", 1, description="OpenAI drop-in")
    resp = client.chat.completions.create(
        model="gpt-4", messages=[{"role": "user", "content": "Hello"}]
    )
    assert resp.model == "gpt-4"          # the alias is echoed, not the slug

    # Repoint, disable, or drop it — clients keep the same name throughout.
    client.admin.aliases.update(alias.id, model_id=2)
    client.admin.aliases.update(alias.id, enabled=False)
    client.admin.aliases.delete(alias.id)

    for m in client.models.list().data:
        if m.alias_of:
            print(f"{m.id} -> {m.alias_of}")
```

Async variants follow the usual `a` prefix: `alist`, `acreate`, `aupdate`, `adelete`.
