sthai 0.1.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.
- sthai-0.1.0/LICENSE +21 -0
- sthai-0.1.0/PKG-INFO +211 -0
- sthai-0.1.0/README.md +184 -0
- sthai-0.1.0/pyproject.toml +51 -0
- sthai-0.1.0/sthai/__init__.py +12 -0
- sthai-0.1.0/sthai/client.py +563 -0
- sthai-0.1.0/sthai/const.py +52 -0
- sthai-0.1.0/sthai/models.py +13 -0
- sthai-0.1.0/sthai/py.typed +0 -0
- sthai-0.1.0/sthai/structs/__init__.py +101 -0
- sthai-0.1.0/sthai/structs/common.py +23 -0
- sthai-0.1.0/sthai/structs/completions.py +303 -0
- sthai-0.1.0/sthai/structs/embeddings.py +127 -0
- sthai-0.1.0/sthai/structs/models.py +39 -0
- sthai-0.1.0/sthai/structs/rerank.py +95 -0
- sthai-0.1.0/sthai/typing.py +22 -0
sthai-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jordan Russell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sthai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sthai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the SiteHost AI Platform: inference, embeddings and reranking
|
|
5
|
+
Keywords: sitehost,ai,llm,inference,embeddings,reranking,vllm
|
|
6
|
+
Author: Jordan Russell
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: msgspec>=0.21.1
|
|
21
|
+
Requires-Dist: niquests>=3.20.1
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Project-URL: Repository, https://github.com/ftsartek/sthai-py
|
|
24
|
+
Project-URL: Issues, https://github.com/ftsartek/sthai-py/issues
|
|
25
|
+
Project-URL: SiteHost AI Platform, https://kb.sitehost.nz/ai-platform
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# sthai-py
|
|
29
|
+
|
|
30
|
+
[](https://github.com/ftsartek/sthai-py/actions/workflows/tests.yml)
|
|
31
|
+
|
|
32
|
+
A Python client for the [SiteHost AI Platform](https://kb.sitehost.nz/ai-platform): inference, embeddings and reranking with typed requests and responses built on [msgspec](https://jcristharif.com/msgspec/).
|
|
33
|
+
|
|
34
|
+
## The SiteHost AI Platform
|
|
35
|
+
|
|
36
|
+
The [SiteHost AI Platform](https://kb.sitehost.nz/ai-platform) serves capable open-weight models at their full context windows - not heavily quantised cut-downs - with full control over system prompts and outputs. Everything runs on SiteHost's own hardware in their own New Zealand data centres, so your data never leaves the country, and request bodies are never stored: only usage metrics are kept for billing and performance monitoring.
|
|
37
|
+
|
|
38
|
+
Models are stable targets, too: each served model has a minimum one-year retention window and at least three months' deprecation notice, with guidance on any adjustments needed.
|
|
39
|
+
|
|
40
|
+
The platform currently serves three models, one per capability (see the [models page](https://kb.sitehost.nz/ai-platform/models) for the source of truth):
|
|
41
|
+
|
|
42
|
+
| Model | Purpose | Context window |
|
|
43
|
+
|-------|---------|----------------|
|
|
44
|
+
| `Qwen/Qwen3.6-27B` | Inference (chat, multimodal, thinking) | 262K |
|
|
45
|
+
| `Qwen/Qwen3-VL-Embedding-8B` | Embeddings (multimodal, Matryoshka, 4096 dims) | 32K |
|
|
46
|
+
| `Qwen/Qwen3-VL-Reranker-8B` | Reranking (multimodal, instruction-trained) | 32K |
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Requires Python 3.10+. Install from [PyPI](https://pypi.org/project/sthai/):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install sthai
|
|
54
|
+
# or, in a uv project
|
|
55
|
+
uv add sthai
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Getting started
|
|
59
|
+
|
|
60
|
+
Create an API key in the SiteHost Control Panel (see [API keys](https://kb.sitehost.nz/ai-platform/api-keys)). The client reads it from the `STHAI_KEY` environment variable, or you can pass `api_key=` explicitly:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from sthai import Client
|
|
64
|
+
|
|
65
|
+
client = Client() # or Client(api_key="...")
|
|
66
|
+
response = client.chat("What's the tallest mountain in New Zealand?")
|
|
67
|
+
print(response.output().text)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### Chat and history
|
|
73
|
+
|
|
74
|
+
`chat()` keeps a conversation going: each successful call appends the user and assistant turns to the client's history, and later calls send it back. The system prompt is applied per call rather than stored.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
client.chat("I'm planning a tramping trip to Fiordland.")
|
|
78
|
+
client.chat("What should I pack?") # the model sees the earlier turn
|
|
79
|
+
|
|
80
|
+
client.chat("Standalone question.", use_history=False) # neither sends nor records
|
|
81
|
+
client.clear_history()
|
|
82
|
+
|
|
83
|
+
client.chat("Be brief: why is the sky blue?", system_prompt="You are terse.")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Thinking models can reason before answering; the reasoning rides along on the response:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
response = client.chat("What is 17 * 23?", use_thinking=True, max_tokens=2000)
|
|
90
|
+
print(response.output().reasoning) # or client.last_reasoning()
|
|
91
|
+
print(response.output().text)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Images
|
|
95
|
+
|
|
96
|
+
Chat and embedding inputs can include images, given as URLs or as local files/bytes (PNG, JPEG, GIF or WEBP - files are inlined as data URIs):
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from pathlib import Path
|
|
100
|
+
|
|
101
|
+
client.chat("What's in this image?", image_files=[Path("photo.png")])
|
|
102
|
+
client.chat("Compare these.", image_urls=["https://example.com/a.jpg", "https://example.com/b.jpg"])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### One-off and structured responses
|
|
106
|
+
|
|
107
|
+
`response()` mirrors `chat()` without the back-and-forth: the stored history is neither sent nor updated. Pass `response_type=` to get structured output - a msgspec Struct becomes a JSON schema the server enforces during generation, and the decoded, validated instance is returned:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from msgspec import Struct
|
|
111
|
+
|
|
112
|
+
class CityInfo(Struct):
|
|
113
|
+
name: str
|
|
114
|
+
country: str
|
|
115
|
+
population: int
|
|
116
|
+
|
|
117
|
+
city = client.response(
|
|
118
|
+
"Give me basic facts about Wellington.",
|
|
119
|
+
response_type=CityInfo,
|
|
120
|
+
)
|
|
121
|
+
print(city.population)
|
|
122
|
+
|
|
123
|
+
data = client.response("List three NZ birds as JSON.", response_type=dict)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If the output is cut off by the token limit, or (rarely, with thinking enabled) the server skips the schema, parsing raises a `ValueError` naming the cause.
|
|
127
|
+
|
|
128
|
+
### Embeddings
|
|
129
|
+
|
|
130
|
+
`embed()` turns one input - text, images, or both - into a single vector. The embedding model is instruction-trained: document embedding is the default, and `query=True` switches to the query instruction for search-style lookups. `dimensions=` truncates the vector server-side (Matryoshka - powers of two work best):
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
vector = client.embed("The Beehive is New Zealand's parliament building.")
|
|
134
|
+
query_vector = client.embed("Where does NZ parliament sit?", query=True)
|
|
135
|
+
small = client.embed("Compact vector, please.", dimensions=512)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`batch_embed()` embeds many texts in one request, returning one vector per text in order:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
vectors = client.batch_embed([
|
|
142
|
+
"Wellington is the capital of New Zealand.",
|
|
143
|
+
"Auckland is the largest city in New Zealand.",
|
|
144
|
+
])
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Reranking
|
|
148
|
+
|
|
149
|
+
`rerank()` scores each document against a query and returns results sorted by relevance, with each result's `index` mapping back to your input list:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
results = client.rerank(
|
|
153
|
+
"What is the capital of New Zealand?",
|
|
154
|
+
[
|
|
155
|
+
"The capital of New Zealand is Wellington.",
|
|
156
|
+
"Auckland has the largest population in New Zealand.",
|
|
157
|
+
"The All Blacks are New Zealand's national rugby team.",
|
|
158
|
+
],
|
|
159
|
+
top_n=2,
|
|
160
|
+
)
|
|
161
|
+
for result in results:
|
|
162
|
+
print(result.relevance_score, result.document.text)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Pass `instruction=` to steer relevance for a specific task; the model applies a sensible default otherwise.
|
|
166
|
+
|
|
167
|
+
### Response helpers
|
|
168
|
+
|
|
169
|
+
Every response type has `usage()` (input/output/cached token counts) and `output()` (the useful payload). The full struct from the most recent inference call is available via `last_response()`:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
response = client.chat("Hello!")
|
|
173
|
+
print(response.usage().input_tokens, response.usage().output_tokens)
|
|
174
|
+
print(client.last_response().model)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Sessions
|
|
178
|
+
|
|
179
|
+
Pinning requests to a server session keeps routing consistent and helps caching. Pass `session_pin=` with your own identifier, or let the client generate one:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
client = Client(auto_session=True)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Models and health
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
client.healthy() # True if the server is up
|
|
189
|
+
for card in client.models():
|
|
190
|
+
print(card.id)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
Development uses [uv](https://docs.astral.sh/uv/). The test suite runs entirely offline against fixtures captured from the live API:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
git clone https://github.com/ftsartek/sthai-py.git
|
|
199
|
+
cd sthai-py
|
|
200
|
+
uv sync
|
|
201
|
+
uv run pytest
|
|
202
|
+
uv run ruff check sthai/ tests/
|
|
203
|
+
uv run ruff format --check sthai/ tests/
|
|
204
|
+
uv run ty check sthai/ tests/
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
To refresh the fixtures against the live API (costs tokens, needs a real key), see `tests/capture_fixtures.py`.
|
|
208
|
+
|
|
209
|
+
## Licence
|
|
210
|
+
|
|
211
|
+
[MIT](LICENSE)
|
sthai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# sthai-py
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ftsartek/sthai-py/actions/workflows/tests.yml)
|
|
4
|
+
|
|
5
|
+
A Python client for the [SiteHost AI Platform](https://kb.sitehost.nz/ai-platform): inference, embeddings and reranking with typed requests and responses built on [msgspec](https://jcristharif.com/msgspec/).
|
|
6
|
+
|
|
7
|
+
## The SiteHost AI Platform
|
|
8
|
+
|
|
9
|
+
The [SiteHost AI Platform](https://kb.sitehost.nz/ai-platform) serves capable open-weight models at their full context windows - not heavily quantised cut-downs - with full control over system prompts and outputs. Everything runs on SiteHost's own hardware in their own New Zealand data centres, so your data never leaves the country, and request bodies are never stored: only usage metrics are kept for billing and performance monitoring.
|
|
10
|
+
|
|
11
|
+
Models are stable targets, too: each served model has a minimum one-year retention window and at least three months' deprecation notice, with guidance on any adjustments needed.
|
|
12
|
+
|
|
13
|
+
The platform currently serves three models, one per capability (see the [models page](https://kb.sitehost.nz/ai-platform/models) for the source of truth):
|
|
14
|
+
|
|
15
|
+
| Model | Purpose | Context window |
|
|
16
|
+
|-------|---------|----------------|
|
|
17
|
+
| `Qwen/Qwen3.6-27B` | Inference (chat, multimodal, thinking) | 262K |
|
|
18
|
+
| `Qwen/Qwen3-VL-Embedding-8B` | Embeddings (multimodal, Matryoshka, 4096 dims) | 32K |
|
|
19
|
+
| `Qwen/Qwen3-VL-Reranker-8B` | Reranking (multimodal, instruction-trained) | 32K |
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Requires Python 3.10+. Install from [PyPI](https://pypi.org/project/sthai/):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install sthai
|
|
27
|
+
# or, in a uv project
|
|
28
|
+
uv add sthai
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Getting started
|
|
32
|
+
|
|
33
|
+
Create an API key in the SiteHost Control Panel (see [API keys](https://kb.sitehost.nz/ai-platform/api-keys)). The client reads it from the `STHAI_KEY` environment variable, or you can pass `api_key=` explicitly:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from sthai import Client
|
|
37
|
+
|
|
38
|
+
client = Client() # or Client(api_key="...")
|
|
39
|
+
response = client.chat("What's the tallest mountain in New Zealand?")
|
|
40
|
+
print(response.output().text)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Chat and history
|
|
46
|
+
|
|
47
|
+
`chat()` keeps a conversation going: each successful call appends the user and assistant turns to the client's history, and later calls send it back. The system prompt is applied per call rather than stored.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
client.chat("I'm planning a tramping trip to Fiordland.")
|
|
51
|
+
client.chat("What should I pack?") # the model sees the earlier turn
|
|
52
|
+
|
|
53
|
+
client.chat("Standalone question.", use_history=False) # neither sends nor records
|
|
54
|
+
client.clear_history()
|
|
55
|
+
|
|
56
|
+
client.chat("Be brief: why is the sky blue?", system_prompt="You are terse.")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Thinking models can reason before answering; the reasoning rides along on the response:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
response = client.chat("What is 17 * 23?", use_thinking=True, max_tokens=2000)
|
|
63
|
+
print(response.output().reasoning) # or client.last_reasoning()
|
|
64
|
+
print(response.output().text)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Images
|
|
68
|
+
|
|
69
|
+
Chat and embedding inputs can include images, given as URLs or as local files/bytes (PNG, JPEG, GIF or WEBP - files are inlined as data URIs):
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
|
|
74
|
+
client.chat("What's in this image?", image_files=[Path("photo.png")])
|
|
75
|
+
client.chat("Compare these.", image_urls=["https://example.com/a.jpg", "https://example.com/b.jpg"])
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### One-off and structured responses
|
|
79
|
+
|
|
80
|
+
`response()` mirrors `chat()` without the back-and-forth: the stored history is neither sent nor updated. Pass `response_type=` to get structured output - a msgspec Struct becomes a JSON schema the server enforces during generation, and the decoded, validated instance is returned:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from msgspec import Struct
|
|
84
|
+
|
|
85
|
+
class CityInfo(Struct):
|
|
86
|
+
name: str
|
|
87
|
+
country: str
|
|
88
|
+
population: int
|
|
89
|
+
|
|
90
|
+
city = client.response(
|
|
91
|
+
"Give me basic facts about Wellington.",
|
|
92
|
+
response_type=CityInfo,
|
|
93
|
+
)
|
|
94
|
+
print(city.population)
|
|
95
|
+
|
|
96
|
+
data = client.response("List three NZ birds as JSON.", response_type=dict)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If the output is cut off by the token limit, or (rarely, with thinking enabled) the server skips the schema, parsing raises a `ValueError` naming the cause.
|
|
100
|
+
|
|
101
|
+
### Embeddings
|
|
102
|
+
|
|
103
|
+
`embed()` turns one input - text, images, or both - into a single vector. The embedding model is instruction-trained: document embedding is the default, and `query=True` switches to the query instruction for search-style lookups. `dimensions=` truncates the vector server-side (Matryoshka - powers of two work best):
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
vector = client.embed("The Beehive is New Zealand's parliament building.")
|
|
107
|
+
query_vector = client.embed("Where does NZ parliament sit?", query=True)
|
|
108
|
+
small = client.embed("Compact vector, please.", dimensions=512)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`batch_embed()` embeds many texts in one request, returning one vector per text in order:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
vectors = client.batch_embed([
|
|
115
|
+
"Wellington is the capital of New Zealand.",
|
|
116
|
+
"Auckland is the largest city in New Zealand.",
|
|
117
|
+
])
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Reranking
|
|
121
|
+
|
|
122
|
+
`rerank()` scores each document against a query and returns results sorted by relevance, with each result's `index` mapping back to your input list:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
results = client.rerank(
|
|
126
|
+
"What is the capital of New Zealand?",
|
|
127
|
+
[
|
|
128
|
+
"The capital of New Zealand is Wellington.",
|
|
129
|
+
"Auckland has the largest population in New Zealand.",
|
|
130
|
+
"The All Blacks are New Zealand's national rugby team.",
|
|
131
|
+
],
|
|
132
|
+
top_n=2,
|
|
133
|
+
)
|
|
134
|
+
for result in results:
|
|
135
|
+
print(result.relevance_score, result.document.text)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Pass `instruction=` to steer relevance for a specific task; the model applies a sensible default otherwise.
|
|
139
|
+
|
|
140
|
+
### Response helpers
|
|
141
|
+
|
|
142
|
+
Every response type has `usage()` (input/output/cached token counts) and `output()` (the useful payload). The full struct from the most recent inference call is available via `last_response()`:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
response = client.chat("Hello!")
|
|
146
|
+
print(response.usage().input_tokens, response.usage().output_tokens)
|
|
147
|
+
print(client.last_response().model)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Sessions
|
|
151
|
+
|
|
152
|
+
Pinning requests to a server session keeps routing consistent and helps caching. Pass `session_pin=` with your own identifier, or let the client generate one:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
client = Client(auto_session=True)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Models and health
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
client.healthy() # True if the server is up
|
|
162
|
+
for card in client.models():
|
|
163
|
+
print(card.id)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
Development uses [uv](https://docs.astral.sh/uv/). The test suite runs entirely offline against fixtures captured from the live API:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
git clone https://github.com/ftsartek/sthai-py.git
|
|
172
|
+
cd sthai-py
|
|
173
|
+
uv sync
|
|
174
|
+
uv run pytest
|
|
175
|
+
uv run ruff check sthai/ tests/
|
|
176
|
+
uv run ruff format --check sthai/ tests/
|
|
177
|
+
uv run ty check sthai/ tests/
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
To refresh the fixtures against the live API (costs tokens, needs a real key), see `tests/capture_fixtures.py`.
|
|
181
|
+
|
|
182
|
+
## Licence
|
|
183
|
+
|
|
184
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sthai"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python client for the SiteHost AI Platform: inference, embeddings and reranking"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Jordan Russell" }]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
keywords = ["sitehost", "ai", "llm", "inference", "embeddings", "reranking", "vllm"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"msgspec>=0.21.1",
|
|
26
|
+
"niquests>=3.20.1",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Repository = "https://github.com/ftsartek/sthai-py"
|
|
31
|
+
Issues = "https://github.com/ftsartek/sthai-py/issues"
|
|
32
|
+
"SiteHost AI Platform" = "https://kb.sitehost.nz/ai-platform"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["uv_build>=0.10,<0.11"]
|
|
36
|
+
build-backend = "uv_build"
|
|
37
|
+
|
|
38
|
+
[tool.uv.build-backend]
|
|
39
|
+
# Flat layout: the sthai package sits at the repository root, not under src/
|
|
40
|
+
module-name = "sthai"
|
|
41
|
+
module-root = ""
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=8",
|
|
49
|
+
"ruff>=0.15.22",
|
|
50
|
+
"ty>=0.0.60",
|
|
51
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Python client for the SiteHost AI Platform (inference, embeddings, reranking)."""
|
|
2
|
+
|
|
3
|
+
from sthai.client import Client, image_content
|
|
4
|
+
from sthai.models import EmbeddingModel, InferenceModel, RerankingModel
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Client",
|
|
8
|
+
"EmbeddingModel",
|
|
9
|
+
"InferenceModel",
|
|
10
|
+
"RerankingModel",
|
|
11
|
+
"image_content",
|
|
12
|
+
]
|