xaikit-py 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.
- xaikit_py-0.1.0/.gitignore +13 -0
- xaikit_py-0.1.0/LICENSE +21 -0
- xaikit_py-0.1.0/PKG-INFO +624 -0
- xaikit_py-0.1.0/README.md +592 -0
- xaikit_py-0.1.0/pyproject.toml +70 -0
- xaikit_py-0.1.0/src/xaikit/__init__.py +231 -0
- xaikit_py-0.1.0/src/xaikit/__main__.py +16 -0
- xaikit_py-0.1.0/src/xaikit/async_client.py +2542 -0
- xaikit_py-0.1.0/src/xaikit/batch.py +402 -0
- xaikit_py-0.1.0/src/xaikit/catalog.py +1290 -0
- xaikit_py-0.1.0/src/xaikit/client.py +4086 -0
- xaikit_py-0.1.0/src/xaikit/collections.py +393 -0
- xaikit_py-0.1.0/src/xaikit/connect.py +106 -0
- xaikit_py-0.1.0/src/xaikit/credentials.py +50 -0
- xaikit_py-0.1.0/src/xaikit/gaps.py +519 -0
- xaikit_py-0.1.0/src/xaikit/pricing.py +269 -0
- xaikit_py-0.1.0/src/xaikit/provider.py +939 -0
- xaikit_py-0.1.0/src/xaikit/py.typed +0 -0
- xaikit_py-0.1.0/src/xaikit/realtime.py +557 -0
- xaikit_py-0.1.0/src/xaikit/retry.py +218 -0
- xaikit_py-0.1.0/src/xaikit/stt_stream.py +613 -0
- xaikit_py-0.1.0/src/xaikit/traces.py +172 -0
- xaikit_py-0.1.0/src/xaikit/tts_stream.py +773 -0
- xaikit_py-0.1.0/src/xaikit/types.py +75 -0
- xaikit_py-0.1.0/src/xaikit/usage.py +477 -0
- xaikit_py-0.1.0/src/xaikit/video.py +109 -0
- xaikit_py-0.1.0/tests/conftest.py +10 -0
- xaikit_py-0.1.0/tests/test_async_client_wiring.py +575 -0
- xaikit_py-0.1.0/tests/test_batch_wiring.py +358 -0
- xaikit_py-0.1.0/tests/test_catalog.py +810 -0
- xaikit_py-0.1.0/tests/test_chat_knobs.py +359 -0
- xaikit_py-0.1.0/tests/test_chat_tools.py +322 -0
- xaikit_py-0.1.0/tests/test_collections_wiring.py +373 -0
- xaikit_py-0.1.0/tests/test_connect_auth.py +324 -0
- xaikit_py-0.1.0/tests/test_deferred_chat_wiring.py +299 -0
- xaikit_py-0.1.0/tests/test_embed_wiring.py +261 -0
- xaikit_py-0.1.0/tests/test_files_wiring.py +406 -0
- xaikit_py-0.1.0/tests/test_gaps.py +143 -0
- xaikit_py-0.1.0/tests/test_live_smoke.py +316 -0
- xaikit_py-0.1.0/tests/test_media_wiring.py +982 -0
- xaikit_py-0.1.0/tests/test_meter_mock.py +109 -0
- xaikit_py-0.1.0/tests/test_otel_usage_sink.py +182 -0
- xaikit_py-0.1.0/tests/test_pricing.py +61 -0
- xaikit_py-0.1.0/tests/test_realtime_client_secrets.py +272 -0
- xaikit_py-0.1.0/tests/test_realtime_wiring.py +393 -0
- xaikit_py-0.1.0/tests/test_responses_wiring.py +424 -0
- xaikit_py-0.1.0/tests/test_stream_traces.py +122 -0
- xaikit_py-0.1.0/tests/test_stt_stream_wiring.py +350 -0
- xaikit_py-0.1.0/tests/test_tokenize_wiring.py +220 -0
- xaikit_py-0.1.0/tests/test_tts_stream_wiring.py +373 -0
- xaikit_py-0.1.0/tests/test_tts_voices_wiring.py +292 -0
- xaikit_py-0.1.0/tests/test_video_wiring.py +898 -0
- xaikit_py-0.1.0/tests/test_watch_xai_models.py +61 -0
xaikit_py-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brian Lowe
|
|
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.
|
xaikit_py-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: xaikit-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python kit for the xAI (Grok) API: typed client, catalog, usage metering, media, and realtime voice
|
|
5
|
+
Project-URL: Homepage, https://github.com/BrianCLowe/xAIkit
|
|
6
|
+
Project-URL: Repository, https://github.com/BrianCLowe/xAIkit
|
|
7
|
+
Project-URL: Issues, https://github.com/BrianCLowe/xAIkit/issues
|
|
8
|
+
Project-URL: Release notes, https://github.com/BrianCLowe/xAIkit/releases
|
|
9
|
+
Author: Brian Lowe
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: grok,grok-api,llm,python,sdk,xai
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: <4.0,>=3.10
|
|
25
|
+
Requires-Dist: httpx>=0.27.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0
|
|
27
|
+
Requires-Dist: websockets<17,>=14.0
|
|
28
|
+
Requires-Dist: xai-sdk>=1.17.0
|
|
29
|
+
Provides-Extra: otel
|
|
30
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="https://raw.githubusercontent.com/BrianCLowe/xAIkit/master/docs/readme-header.jpg" alt="xAIkit — kit for interacting with xAI models and APIs" width="100%" />
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
# xAIkit
|
|
38
|
+
|
|
39
|
+
**`0.1.0`.** Not an official xAI package. The API may still change. Problems or feedback: [open an issue](https://github.com/BrianCLowe/xAIkit/issues).
|
|
40
|
+
|
|
41
|
+
**Unofficial** Python kit for the **xAI (Grok) API** — one typed client, living model catalog, usage metering, media, and realtime voice. Not a multi-provider SDK.
|
|
42
|
+
|
|
43
|
+
Requires **Python 3.10+**, the same floor as the official [xAI Python SDK](https://github.com/xai-org/xai-sdk-python).
|
|
44
|
+
|
|
45
|
+
Built for apps that want a typed Grok/xAI client with purpose-tagged usage events, catalog resolve (`cheapest` / `economy` / `best`), inject credential stores, and a mock provider for CI — without pulling in a multi-provider marketplace.
|
|
46
|
+
|
|
47
|
+
## What it does
|
|
48
|
+
|
|
49
|
+
A **Python library for the Grok / xAI API** — one typed client so your app can chat, generate images and video, speak, search collections, and meter what each feature costs. Not a chatbot UI. Not LiteLLM, OpenRouter, or another multi-provider gateway. Just xAI, as a kit you drop into an existing codebase.
|
|
50
|
+
|
|
51
|
+
| You want to… | xAIkit gives you |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| **Call Grok from Python** (chat, stream, tools, vision, structured JSON) | `XaiClient` and `AsyncXaiClient` — same method names; JSON dicts, not protobuf |
|
|
54
|
+
| **Generate or edit images**, make video, or do speech on xAI | Imagine generate/edit, video + extend, REST + streaming STT/TTS, realtime speech-to-speech |
|
|
55
|
+
| **Pick a model** without hardcoding IDs that churn | `resolve_model("cheapest" \| "economy" \| "best")` per role (`chat` / `image` / `video` / `voice`); `pin=` still wins |
|
|
56
|
+
| **See what a feature costs** (tokens, estimated USD, OpenTelemetry) | Purpose-tagged `UsageMeter` — `purpose=` is required when a meter is attached |
|
|
57
|
+
| **Test without an API key** or live spend | `MockChatProvider` + `inject_catalog` — CI stays offline |
|
|
58
|
+
| **Keep keys out of the browser** for realtime voice | Server-side `create_realtime_client_secret` (and the `sec-websocket-protocol` helper) |
|
|
59
|
+
| **Bring your own credentials / IdP** | `api_key=` or a `CredentialStore`; OAuth URLs are caller-supplied, never hardcoded |
|
|
60
|
+
| **Use the rest of the xAI surface** | Files, embeddings, tokenize, batch, collections, Responses, priority and deferred chat |
|
|
61
|
+
|
|
62
|
+
Not an official xAI package. Domain schemas and the tool loop stay in your app.
|
|
63
|
+
|
|
64
|
+
## How this differs from the official SDK
|
|
65
|
+
|
|
66
|
+
The official [xAI Python SDK](https://github.com/xai-org/xai-sdk-python) (`xai_sdk`) is the gRPC client xAI ships. xAIkit **uses it for live chat** and adds a kit around it:
|
|
67
|
+
|
|
68
|
+
| | Official SDK | xAIkit |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Status | Official | Unofficial kit (this repo / `xaikit-py`) |
|
|
71
|
+
| Chat live path | `xai_sdk.Client` / protobuf | Same SDK under `XaiClient`; you see **JSON dicts**, not protobuf |
|
|
72
|
+
| Tests without a key | You mock gRPC | `MockChatProvider` + `inject_catalog` |
|
|
73
|
+
| Model pick | You hardcode ids | `resolve_model("cheapest" \| "economy" \| "best")` per role |
|
|
74
|
+
| Cost | None | Purpose-tagged `UsageMeter` + a copied public price table (estimates, not invoices) |
|
|
75
|
+
| REST / WS extras | Separate HTTP/WS samples | Image, video, STT/TTS, realtime voice, Files, batch, collections on the same client |
|
|
76
|
+
| Auth | API key | API key, `CredentialStore`, or **caller-supplied** OAuth URLs — no User types, no grok.com login |
|
|
77
|
+
|
|
78
|
+
Install stays `xaikit-py`; `import xaikit`. You can use both packages in one app.
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
The import stays `xaikit` (the PyPI name is `xaikit-py` because `xaikit` was too close to an existing explainable-AI project).
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv add xaikit-py
|
|
86
|
+
# or: pip install xaikit-py
|
|
87
|
+
|
|
88
|
+
# From a git tag
|
|
89
|
+
uv add "xaikit-py @ git+https://github.com/BrianCLowe/xAIkit@v0.1.0"
|
|
90
|
+
|
|
91
|
+
# Editable neighbor checkout
|
|
92
|
+
uv add --editable ../xAIkit
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Problems and feedback
|
|
96
|
+
|
|
97
|
+
If something breaks, the docs are wrong, or an API is missing, [open an issue](https://github.com/BrianCLowe/xAIkit/issues). Include the package version (`python -c "import xaikit; print(xaikit.__version__)"`), how you installed (PyPI / git / editable), and a short repro. Redact API keys.
|
|
98
|
+
|
|
99
|
+
## Quick usage
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from xaikit import (
|
|
103
|
+
InMemoryUsageSink,
|
|
104
|
+
MockChatProvider,
|
|
105
|
+
UsageMeter,
|
|
106
|
+
XaiClient,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
meter = UsageMeter(sink=InMemoryUsageSink())
|
|
110
|
+
client = XaiClient(
|
|
111
|
+
provider=MockChatProvider(replies="hi"),
|
|
112
|
+
model="grok-4.5",
|
|
113
|
+
usage_meter=meter,
|
|
114
|
+
)
|
|
115
|
+
resp = client.chat([{"role": "user", "content": "hello"}], purpose="demo.chat")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Async twin (`AsyncXaiClient`) uses the same method names; REST uses `httpx.AsyncClient` and live chat uses `xai_sdk.AsyncClient`. `MockChatProvider` works with both clients:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import asyncio
|
|
122
|
+
from xaikit import AsyncXaiClient, MockChatProvider
|
|
123
|
+
|
|
124
|
+
async def main() -> None:
|
|
125
|
+
client = AsyncXaiClient(provider=MockChatProvider(replies="hi"), model="grok-4.5")
|
|
126
|
+
resp = await client.chat([{"role": "user", "content": "hello"}])
|
|
127
|
+
print(resp.content)
|
|
128
|
+
|
|
129
|
+
asyncio.run(main())
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
When `usage_meter` is attached, **purpose is required**. Without a meter, purpose is optional.
|
|
133
|
+
|
|
134
|
+
### Pricing estimates *(not invoices)*
|
|
135
|
+
|
|
136
|
+
`UsageMeter` can attach a `PriceTable`. The bootstrap table is a **manual copy** of xAI’s public list prices — not a live billing feed and not an invoice.
|
|
137
|
+
|
|
138
|
+
- **Source:** [docs.x.ai/developers/pricing](https://docs.x.ai/developers/pricing) (chat also [docs.x.ai/docs/models](https://docs.x.ai/docs/models))
|
|
139
|
+
- **Last copied into the kit:** `default_price_table().fetched` (also `PRICE_TABLE_FETCHED`)
|
|
140
|
+
- **Refresh:** re-read those pages and update the dicts in `src/xaikit/pricing.py` (kit release), **or** overlay JSON with `load_price_table("prices.json")` / `save_price_table_template("prices.json")` without waiting on a kit bump. There is no auto-fetch on import.
|
|
141
|
+
- **No public rate → no invented USD.** Embeddings, tokenizer, batch, collections, Responses, Files, REST TTS, and similar still record purpose/tokens/success; `estimated_usd` stays unset.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from xaikit import PRICE_TABLE_FETCHED, PRICE_TABLE_SOURCE_URL, default_price_table
|
|
145
|
+
|
|
146
|
+
table = default_price_table()
|
|
147
|
+
assert table.source_url == PRICE_TABLE_SOURCE_URL
|
|
148
|
+
assert table.fetched == PRICE_TABLE_FETCHED
|
|
149
|
+
# table.price_for("grok-4.6").input_per_million
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Optional OpenTelemetry export (`pip install 'xaikit-py[otel]'`): `OpenTelemetryUsageSink` increments `xaikit.usage.calls` / `xaikit.usage.tokens` (attributes: purpose, model, modality, success). It is export-only — pair with `InMemoryUsageSink` via `CompositeUsageSink` to inspect events.
|
|
153
|
+
|
|
154
|
+
## Credentials and OAuth
|
|
155
|
+
|
|
156
|
+
Pass `api_key=` or a `CredentialStore` (`EnvCredentialStore` / `DictCredentialStore`). The kit does **not** hardcode xAI portal URLs. OAuth helpers take **caller-supplied** `authorize_url` and `token_url`.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from xaikit import (
|
|
160
|
+
build_oauth_authorize_url,
|
|
161
|
+
exchange_oauth_code,
|
|
162
|
+
oauth_is_configured,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Your app supplies the IdP endpoints — not the kit.
|
|
166
|
+
authorize = "https://idp.example.com/authorize"
|
|
167
|
+
token = "https://idp.example.com/token"
|
|
168
|
+
|
|
169
|
+
assert oauth_is_configured(client_id="app-id", client_secret="app-secret")
|
|
170
|
+
url = build_oauth_authorize_url(
|
|
171
|
+
client_id="app-id",
|
|
172
|
+
redirect_uri="https://app.example.com/callback",
|
|
173
|
+
state="nonce-1",
|
|
174
|
+
authorize_url=authorize,
|
|
175
|
+
)
|
|
176
|
+
# tokens = exchange_oauth_code(
|
|
177
|
+
# code,
|
|
178
|
+
# client_id="app-id",
|
|
179
|
+
# client_secret="app-secret",
|
|
180
|
+
# redirect_uri="https://app.example.com/callback",
|
|
181
|
+
# token_url=token,
|
|
182
|
+
# )
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This is **not** grok.com / SuperGrok login. A Grok subscription’s **weekly usage pool** (Settings → Usage in the Grok app) has no public API. The kit does not scrape it, does not show “how much you have left,” and does not mint User/Session objects. `UsageMeter` only records calls **your app** made through `XaiClient`. Team API prepaid remaining lives in the [xAI Console](https://console.x.ai) Usage Explorer (a management-key billing API, not OAuth).
|
|
186
|
+
|
|
187
|
+
## Catalog resolve
|
|
188
|
+
|
|
189
|
+
Callers pass `cheapest` / `economy` / `best` (and optional `role=`). Chat is the default pool. Pin still wins when `pin=` is set.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from xaikit import ModelInfo, feature_options, inject_catalog, resolve_model, resolve_model_selection
|
|
193
|
+
|
|
194
|
+
inject_catalog(
|
|
195
|
+
[
|
|
196
|
+
ModelInfo(id="grok-4.6", capabilities=["chat"], input_per_million=20.0, created=2),
|
|
197
|
+
ModelInfo(id="grok-imagine-image", capabilities=["image"], created=1),
|
|
198
|
+
ModelInfo(id="grok-imagine-image-quality", capabilities=["image"], created=2),
|
|
199
|
+
]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
chat_id = resolve_model(intent="economy") # role="chat" default
|
|
203
|
+
image = resolve_model_selection(intent="best", role="image")
|
|
204
|
+
video_id = resolve_model(intent="cheapest", role="video")
|
|
205
|
+
voice_id = resolve_model(intent="economy", role="voice")
|
|
206
|
+
extend_id = resolve_model(intent="best", role="video", need="video_extend")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`role` is `chat` | `image` | `video` | `voice`. Offline tests inject fixtures with `inject_catalog` — do not hit the network.
|
|
210
|
+
|
|
211
|
+
`feature_options(model=)` lists extra capabilities for settings UIs (not role tags). No model → Grok 4.6 chat extras (`web_search`, `x_search`, `code_execution`, `file_attachments`, `collections_search`, `image_understanding`, `x_video_understanding`, `mcp`). Imagine quality (`grok-imagine-video`) reports `video_extend` / `video_edit` / `r2v`; `grok-imagine-video-1.5` reports `1080p` / `r2v` and not extend. Unknown or older SKUs return `[]`. Pass the same ids as `need=` on resolve so `best` is best for that job (quality over 1.5 when the job is extend).
|
|
212
|
+
|
|
213
|
+
When `model` is omitted, chat resolve falls back to `BOOTSTRAP_MODEL` (`grok-4.6`). Offline with no API key or fixture, `list_models` injects `grok-4.6` plus cheaper-band `grok-4.3`. Pass `persist_path=` to write a JSON snapshot after a live SDK fetch and reload it later; there is no default disk path.
|
|
214
|
+
|
|
215
|
+
## Image generation and edit
|
|
216
|
+
|
|
217
|
+
REST Imagine images on `XaiClient` (mocked HTTP in tests; live calls need `XAI_API_KEY`). `edit_image` posts JSON to `/v1/images/edits` (not OpenAI multipart). One source is a public URL, data URI, or a `file_id` from `upload_file`. Pass `images=` (2–3 entries, mixable kinds) for multi-image edit; the prompt may refer to `<IMAGE_0>`, `<IMAGE_1>`, `<IMAGE_2>`.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from xaikit import MockChatProvider, XaiClient
|
|
221
|
+
|
|
222
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
223
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
224
|
+
|
|
225
|
+
out = client.generate_image(
|
|
226
|
+
"A red cube on a table",
|
|
227
|
+
aspect_ratio="1:1",
|
|
228
|
+
resolution="2k",
|
|
229
|
+
response_format="b64_json",
|
|
230
|
+
)
|
|
231
|
+
# quality= ("low" | "medium") is grok-imagine-image-2.0 only
|
|
232
|
+
edited = client.edit_image(
|
|
233
|
+
"Make it a pencil sketch",
|
|
234
|
+
image_url=out["url"], # or image_file_id="file-..."
|
|
235
|
+
)
|
|
236
|
+
# edited["url"] / edited["b64_json"] / edited["file_id"]
|
|
237
|
+
# collage = client.edit_image(
|
|
238
|
+
# "Put <IMAGE_0> in the style of <IMAGE_1>",
|
|
239
|
+
# images=["https://example.com/a.png", {"file_id": "file-style"}],
|
|
240
|
+
# )
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Default model is `grok-imagine-image-quality`. Optional generate knobs: `aspect_ratio` (Imagine list, including `auto` / `19.5:9` / `20:9`), `resolution` (`1k` | `2k`), `response_format` (`b64_json`), and `quality` (`low` | `medium`) on `grok-imagine-image-2.0` only. Unknown aspect/resolution values are omitted. When Imagine returns `file_output.file_id`, both methods surface it as `file_id`.
|
|
244
|
+
|
|
245
|
+
## Files
|
|
246
|
+
|
|
247
|
+
REST Files on `XaiClient` (mocked HTTP in tests). `upload_file` posts multipart to `/v1/files` and returns `{id, filename, bytes, created_at, …}`. Kit `purpose=` is the usage-meter tag; `file_purpose=` (default `"assistants"`) is the upstream multipart field. Optional `get_file` / `delete_file` hit `/v1/files/{file_id}`.
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from xaikit import MockChatProvider, XaiClient
|
|
251
|
+
|
|
252
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
253
|
+
meta = client.upload_file(b"hello", "note.txt", content_type="text/plain")
|
|
254
|
+
# meta["id"] is the opaque file_id
|
|
255
|
+
# client.get_file(meta["id"])
|
|
256
|
+
# client.delete_file(meta["id"])
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Uploads larger than 50 MB are rejected before HTTP.
|
|
260
|
+
|
|
261
|
+
## Embeddings
|
|
262
|
+
|
|
263
|
+
REST embeddings on `XaiClient` (mocked HTTP in tests). `embed` posts JSON to `/v1/embeddings` and returns `{object, model, data, usage}` where `data` is `[{index, embedding}, …]`. `model=` is required — the kit does not invent a default. List live ids with `GET /v1/embedding-models` (OpenAPI’s `v1` is an example and may 404; some teams have an empty roster). Collections index models are not this endpoint. Empty input is rejected before HTTP.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from xaikit import MockChatProvider, XaiClient
|
|
267
|
+
|
|
268
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
269
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
270
|
+
|
|
271
|
+
out = client.embed(["query: hello", "passage: world"], model="your-embed-sku")
|
|
272
|
+
vectors = [row["embedding"] for row in out["data"]]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
When a usage meter is attached, `purpose=` is required. Events use `modality="embed"`. The public pricing table has no embeddings rate, so the meter records tokens without inventing USD.
|
|
276
|
+
|
|
277
|
+
## Tokenizer
|
|
278
|
+
|
|
279
|
+
REST tokenize on `XaiClient` (mocked HTTP in tests). `tokenize` posts JSON to `/v1/tokenize-text` and returns `{tokens, count, model}` where `tokens` is `[{token_id, string, token_bytes}, …]` (plain dicts, not protobuf). `model=` defaults to the client's chat model. Empty text is rejected before HTTP.
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from xaikit import MockChatProvider, XaiClient
|
|
283
|
+
|
|
284
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
285
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
286
|
+
|
|
287
|
+
out = client.tokenize("Hello world")
|
|
288
|
+
n = out["count"]
|
|
289
|
+
pieces = [row["string"] for row in out["tokens"]]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
When a usage meter is attached, `purpose=` is required. Events use `modality="tokenize"`. The public pricing table has no tokenizer rate, so the meter records the token count without inventing USD.
|
|
293
|
+
|
|
294
|
+
## Batch
|
|
295
|
+
|
|
296
|
+
SDK batch on `XaiClient` (mocked helper in tests — never hits gRPC). `create_batch` / `add_batch_requests` submit a job; `get_batch` polls status; `list_batch_results` reads completions as JSON dicts (no protobuf). Requests are chat-shaped dicts (`model`, `messages`, knobs). Live Batch rejects `grok-4.6` and `grok-4.5`; omitted model and those SKUs remap to `grok-4.3` (`need=batch`). Unknown pins stay. Empty name / batch id / requests are rejected before the RPC.
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from xaikit import MockChatProvider, XaiClient
|
|
300
|
+
|
|
301
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
302
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
303
|
+
|
|
304
|
+
job = client.create_batch("nightly-capitals")
|
|
305
|
+
client.add_batch_requests(
|
|
306
|
+
job["id"],
|
|
307
|
+
[
|
|
308
|
+
{
|
|
309
|
+
"messages": [{"role": "user", "content": "Capital of France?"}],
|
|
310
|
+
"batch_request_id": "fr",
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
)
|
|
314
|
+
status = client.get_batch(job["id"])
|
|
315
|
+
# status["state"]["num_pending"] / num_success / …
|
|
316
|
+
# results = client.list_batch_results(job["id"])
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
When a usage meter is attached, `purpose=` is required. Events use `modality="batch"`. The public pricing table has no batch rate, so the meter records purpose/success without inventing USD.
|
|
320
|
+
|
|
321
|
+
## Collections
|
|
322
|
+
|
|
323
|
+
SDK collections on `XaiClient` (mocked helper in tests — never hits gRPC). `create_collection` / `upload_document` / `search_collections` cover the upload-and-query path; `get_collection` / `list_collections` / `delete_collection` are included. Returns JSON dicts (no protobuf). Empty name / collection id / query / file bytes are rejected before the RPC.
|
|
324
|
+
|
|
325
|
+
Live create / get / list / delete / upload use xAI's management API. Set `XAI_MANAGEMENT_KEY` in the environment (the SDK reads it). Search uses the regular API key. This client does not take a second key argument. A collection id can 404 on search until it is visible/indexed on the inference side — the kit does not wait or retry.
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from xaikit import MockChatProvider, XaiClient
|
|
329
|
+
|
|
330
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
331
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"]) # plus XAI_MANAGEMENT_KEY in env
|
|
332
|
+
|
|
333
|
+
coll = client.create_collection("docs")
|
|
334
|
+
# coll["id"] / coll["name"]
|
|
335
|
+
listed = client.list_collections()
|
|
336
|
+
# listed["collections"]
|
|
337
|
+
got = client.get_collection(coll["id"])
|
|
338
|
+
client.upload_document(coll["id"], "note.txt", b"hello world")
|
|
339
|
+
hits = client.search_collections("hello", coll["id"])
|
|
340
|
+
# hits["matches"][0]["chunk_content"] / file_id / score
|
|
341
|
+
# client.delete_collection(coll["id"])
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
When a usage meter is attached, `purpose=` is required. Events use `modality="collections"`. The public pricing table has no collections rate, so the meter records purpose/success without inventing USD.
|
|
345
|
+
|
|
346
|
+
## Video generation
|
|
347
|
+
|
|
348
|
+
REST Imagine video on `XaiClient` (mocked HTTP in tests; live calls need `XAI_API_KEY`). `into=` is required — a `VideoInbox`, list, or callback the app keeps. The kit delivers `request_id` as soon as xAI accepts the job, then the terminal result. Do not rely on the return value alone: a sibling failure can cancel the await (`asyncio.gather` / `TaskGroup`) without voiding the receipt. `inbox.cancel(request_id)` is the only way to stop listening. Default `wait=True` polls until the clip is ready; `wait=False` returns `request_id` for `poll_video`.
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
from xaikit import MockChatProvider, VideoInbox, XaiClient
|
|
352
|
+
|
|
353
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
354
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
355
|
+
|
|
356
|
+
inbox = VideoInbox()
|
|
357
|
+
started = client.generate_video(
|
|
358
|
+
"A red cube rotating on a table",
|
|
359
|
+
duration=8,
|
|
360
|
+
aspect_ratio="16:9",
|
|
361
|
+
resolution="480p",
|
|
362
|
+
into=inbox,
|
|
363
|
+
wait=False,
|
|
364
|
+
)
|
|
365
|
+
status = client.poll_video(started["request_id"])
|
|
366
|
+
# bytes = client.download_video(status["url"]) # when status == "done"
|
|
367
|
+
# status["error"] is set when status is failed / expired (same text wait raises)
|
|
368
|
+
# inbox.receipts still has the ticket if a parallel await is cancelled
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`extend_video(prompt, video_url=..., into=inbox)` continues a clip. Generate defaults to `grok-imagine-video-1.5`; extend remaps that SKU to `grok-imagine-video` (1.5 cannot extend). `1080p` is kept on 1.5 for text-to-video and image-to-video; reference-to-video and older `grok-imagine-video` send `720p` instead.
|
|
372
|
+
|
|
373
|
+
## Realtime voice
|
|
374
|
+
|
|
375
|
+
Speech-to-speech over the documented xAI realtime WebSocket (`wss://api.x.ai/v1/realtime`). No mic, recorder, or playground in this library — apps own capture/playback. Offline tests mock the socket.
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
from xaikit import MockChatProvider, XaiClient, decode_realtime_audio
|
|
379
|
+
|
|
380
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
381
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
382
|
+
|
|
383
|
+
with client.open_realtime_session(
|
|
384
|
+
voice="eve",
|
|
385
|
+
instructions="You are a helpful assistant.",
|
|
386
|
+
) as session:
|
|
387
|
+
session.send_text("Hello!")
|
|
388
|
+
for _ in range(8):
|
|
389
|
+
event = session.recv(timeout=30)
|
|
390
|
+
pcm = decode_realtime_audio(event) if isinstance(event, dict) else None
|
|
391
|
+
if pcm:
|
|
392
|
+
break # app owns playback; this library has no speaker
|
|
393
|
+
# session.send_audio(pcm16_bytes) # app owns capture; this library has no mic
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Default model is `grok-voice-latest`. Constructor `voice_model=` overrides like `video_model=`. Custom `voice_id` strings on `voice=` work the same as built-in names like `eve`. REST STT/TTS stay on `transcribe` / `synthesize_speech`. Streaming STT is `open_stt_session`; streaming TTS is `open_tts_session` (not speech-to-speech).
|
|
397
|
+
|
|
398
|
+
Mint a short-lived token on the **server** so the long-lived API key never reaches the browser. Pass `value` to the client (`Authorization: Bearer <token>`, or `realtime_client_secret_protocol(token)` for `sec-websocket-protocol`).
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
from xaikit import MockChatProvider, XaiClient, realtime_client_secret_protocol
|
|
402
|
+
|
|
403
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
404
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"]) # server-side only
|
|
405
|
+
|
|
406
|
+
secret = client.create_realtime_client_secret(expires_after=300)
|
|
407
|
+
token = secret["value"]
|
|
408
|
+
# Client WS: Authorization: Bearer {token}
|
|
409
|
+
protocol = realtime_client_secret_protocol(token) # "xai-client-secret.{token}"
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Streaming speech-to-text
|
|
413
|
+
|
|
414
|
+
Unary-transcribe over `wss://api.x.ai/v1/stt`. Send raw PCM bytes (not base64). This is not the realtime voice (STS) socket.
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from xaikit import MockChatProvider, XaiClient
|
|
418
|
+
|
|
419
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
420
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
421
|
+
|
|
422
|
+
pcm16_bytes = bytes(3200) # 100 ms of 16 kHz s16le PCM — apps own capture
|
|
423
|
+
with client.open_stt_session(language="en", interim_results=True) as session:
|
|
424
|
+
session.send_audio(pcm16_bytes)
|
|
425
|
+
session.audio_done()
|
|
426
|
+
for event in session.events():
|
|
427
|
+
if event.get("type") == "transcript.partial":
|
|
428
|
+
print(event.get("text"))
|
|
429
|
+
elif event.get("type") == "transcript.done":
|
|
430
|
+
break
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
REST file transcription stays on `transcribe`. Offline tests mock the socket.
|
|
434
|
+
|
|
435
|
+
## Streaming text-to-speech
|
|
436
|
+
|
|
437
|
+
Bidirectional TTS over `wss://api.x.ai/v1/tts`. Send text deltas; receive base64 `audio.delta` chunks. This is not the realtime voice (STS) socket.
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
from xaikit import MockChatProvider, XaiClient, decode_tts_audio
|
|
441
|
+
|
|
442
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
443
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
444
|
+
|
|
445
|
+
with client.open_tts_session(language="en", voice="eve", codec="mp3") as session:
|
|
446
|
+
session.send_text("Hello from streaming TTS.")
|
|
447
|
+
session.text_done()
|
|
448
|
+
for event in session.events():
|
|
449
|
+
chunk = decode_tts_audio(event)
|
|
450
|
+
if chunk:
|
|
451
|
+
pass # apps own playback
|
|
452
|
+
elif event.get("type") == "audio.done":
|
|
453
|
+
break
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
REST unary synthesis stays on `synthesize_speech`. Optional knobs match the streaming set (`codec`, `sample_rate`, `bit_rate`, `speed`, `optimize_streaming_latency`, `text_normalization`, `with_timestamps`, `replace`) and nest `codec` / `sample_rate` / `bit_rate` as `output_format` on the wire. Text over 15,000 characters is rejected before HTTP. `with_timestamps=True` returns a JSON envelope (`application/json`) instead of raw audio bytes.
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
audio, content_type = client.synthesize_speech(
|
|
460
|
+
"Hello from REST TTS.",
|
|
461
|
+
voice_id="eve",
|
|
462
|
+
codec="wav",
|
|
463
|
+
sample_rate=24000,
|
|
464
|
+
speed=1.0,
|
|
465
|
+
)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Offline tests mock the socket.
|
|
469
|
+
|
|
470
|
+
List built-in TTS voices (not team-scoped custom clones):
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
voices = client.list_tts_voices()
|
|
474
|
+
# voices[0]["voice_id"] / ["name"] / ["language"]
|
|
475
|
+
# client.get_tts_voice("eve")
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## Streaming
|
|
479
|
+
|
|
480
|
+
```python
|
|
481
|
+
for chunk in client.chat_stream(
|
|
482
|
+
[{"role": "user", "content": "hello"}],
|
|
483
|
+
purpose="demo.stream",
|
|
484
|
+
):
|
|
485
|
+
print(chunk.delta, end="", flush=True)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
## Tools, vision, and structured JSON
|
|
489
|
+
|
|
490
|
+
The kit wraps xAI chat extras as JSON dicts. It does **not** run tools — the app owns the loop.
|
|
491
|
+
|
|
492
|
+
```python
|
|
493
|
+
from xaikit import MockChatProvider, XaiClient
|
|
494
|
+
|
|
495
|
+
weather_tool = {
|
|
496
|
+
"name": "get_weather",
|
|
497
|
+
"description": "Get the weather for a city.",
|
|
498
|
+
"parameters": {
|
|
499
|
+
"type": "object",
|
|
500
|
+
"properties": {"city": {"type": "string"}},
|
|
501
|
+
"required": ["city"],
|
|
502
|
+
},
|
|
503
|
+
}
|
|
504
|
+
client = XaiClient(
|
|
505
|
+
provider=MockChatProvider(
|
|
506
|
+
replies=[
|
|
507
|
+
"a cube",
|
|
508
|
+
{
|
|
509
|
+
"tool_calls": [
|
|
510
|
+
{"id": "call_1", "name": "get_weather", "arguments": {"city": "NYC"}},
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
{"title": "blue"},
|
|
514
|
+
]
|
|
515
|
+
),
|
|
516
|
+
model="grok-4.5",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# Vision: content may be a string or a list of parts
|
|
520
|
+
client.chat(
|
|
521
|
+
[
|
|
522
|
+
{
|
|
523
|
+
"role": "user",
|
|
524
|
+
"content": [
|
|
525
|
+
{"type": "text", "text": "What is in this image?"},
|
|
526
|
+
{"type": "image_url", "url": "https://example.com/cube.png"},
|
|
527
|
+
],
|
|
528
|
+
}
|
|
529
|
+
]
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Tools: defs in, tool_calls out (arguments are parsed JSON, typically a dict)
|
|
533
|
+
resp = client.chat(
|
|
534
|
+
[{"role": "user", "content": "Weather in NYC?"}],
|
|
535
|
+
tools=[weather_tool],
|
|
536
|
+
tool_choice="auto",
|
|
537
|
+
)
|
|
538
|
+
# resp.tool_calls → [{"id": "call_1", "name": "get_weather", "arguments": {"city": "NYC"}}]
|
|
539
|
+
# App runs the function, then sends the assistant turn + tool result:
|
|
540
|
+
# client.chat([
|
|
541
|
+
# {"role": "user", "content": "Weather in NYC?"},
|
|
542
|
+
# {"role": "assistant", "content": "", "tool_calls": resp.tool_calls},
|
|
543
|
+
# {"role": "tool", "content": "72F", "tool_call_id": resp.tool_calls[0]["id"]},
|
|
544
|
+
# ], tools=[weather_tool])
|
|
545
|
+
|
|
546
|
+
# Native structured outputs (fence-stripping remains the fallback)
|
|
547
|
+
schema = {
|
|
548
|
+
"type": "object",
|
|
549
|
+
"properties": {"title": {"type": "string"}},
|
|
550
|
+
"required": ["title"],
|
|
551
|
+
}
|
|
552
|
+
data = client.chat_json("Name a color", schema=schema)
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## Responses API (built-in tools)
|
|
556
|
+
|
|
557
|
+
Additive REST wrap of `POST /v1/responses`. **Chat remains the default text path** (`chat` / `chat_stream`). Built-in server tools (web search, X search, code interpreter, collections/`file_search`, image generation) are **opt-in** — they are never sent unless you pass `tools=`.
|
|
558
|
+
|
|
559
|
+
```python
|
|
560
|
+
from xaikit import MockChatProvider, XaiClient
|
|
561
|
+
|
|
562
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
563
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
564
|
+
|
|
565
|
+
out = client.create_response(
|
|
566
|
+
"What is 101*3?",
|
|
567
|
+
tools=[{"type": "code_interpreter"}], # omit tools= for text-only
|
|
568
|
+
)
|
|
569
|
+
# out["id"] / out["output"] / out["usage"]
|
|
570
|
+
# client.get_response(out["id"])
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
When a usage meter is attached, `purpose=` is required. Events use `modality="responses"`. The public pricing table has no Responses/tools rate, so the meter records tokens without inventing USD.
|
|
574
|
+
|
|
575
|
+
## Priority processing and deferred chat
|
|
576
|
+
|
|
577
|
+
Optional `service_tier="priority"` (or `"default"`) on `chat` / `chat_stream` / `chat_json` and `create_response`. Omit the knob for default. Invalid values are rejected before the network.
|
|
578
|
+
|
|
579
|
+
Deferred completions are a separate REST pair — not a second return type on `chat`:
|
|
580
|
+
|
|
581
|
+
```python
|
|
582
|
+
from xaikit import MockChatProvider, XaiClient
|
|
583
|
+
|
|
584
|
+
client = XaiClient(provider=MockChatProvider(), api_key="test-key")
|
|
585
|
+
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])
|
|
586
|
+
|
|
587
|
+
ticket = client.create_deferred_chat([{"role": "user", "content": "126/3=?"}])
|
|
588
|
+
# ticket["request_id"]
|
|
589
|
+
# result = client.get_deferred_chat(ticket["request_id"])
|
|
590
|
+
# result["status"] is "pending" (HTTP 202) or "complete" (HTTP 200 + completion fields)
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Create and pending get meter `modality="chat"` without tokens. A complete get may record `usage` tokens. No invented USD.
|
|
594
|
+
|
|
595
|
+
## Opt-in dev completion traces *(default off)*
|
|
596
|
+
|
|
597
|
+
```python
|
|
598
|
+
from xaikit import CompletionTracer, InMemoryTraceSink, MockChatProvider, XaiClient
|
|
599
|
+
|
|
600
|
+
tracer = CompletionTracer(sink=InMemoryTraceSink())
|
|
601
|
+
client = XaiClient(
|
|
602
|
+
provider=MockChatProvider(replies="hi"),
|
|
603
|
+
model="grok-4.5",
|
|
604
|
+
completion_tracer=tracer,
|
|
605
|
+
)
|
|
606
|
+
client.chat([{"role": "user", "content": "hello"}])
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
## Optional gap log *(companion — default off)*
|
|
610
|
+
|
|
611
|
+
```bash
|
|
612
|
+
uv run python -m xaikit.gaps --path ./gaps.jsonl
|
|
613
|
+
# or: xaikit-gaps --path ./gaps.jsonl --kind capability_gap
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## HTTP mounts *(examples/docs only)*
|
|
617
|
+
|
|
618
|
+
Thin FastAPI samples under [`examples/`](examples/) — not required package surface.
|
|
619
|
+
|
|
620
|
+
## License
|
|
621
|
+
|
|
622
|
+
MIT
|
|
623
|
+
|
|
624
|
+
Contributor / agent docs (not part of the installed package): [`docs/Master_Index.md`](docs/Master_Index.md).
|