Skip to main content

xAIkit — kit for interacting with xAI models and APIs

xAIkit

0.1.3. Not an official xAI package. The API may still change. Problems or feedback: open an issue.

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.

Requires Python 3.10+, the same floor as the official xAI Python SDK.

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.

What it does

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.

You want to… xAIkit gives you
Call Grok from Python (chat, stream, tools, vision, structured JSON) XaiClient and AsyncXaiClient — same method names; JSON dicts, not protobuf
Generate or edit images, make video, or do speech on xAI Imagine generate/edit, video + extend, REST + streaming STT/TTS, realtime speech-to-speech
Pick a model without hardcoding IDs that churn resolve_model("cheapest" | "economy" | "best") per role (chat / image / video / voice); pin= still wins
See what a feature costs (tokens, estimated USD, OpenTelemetry) Purpose-tagged UsageMeter — purpose= is required when a meter is attached
Test without an API key or live spend MockChatProvider + inject_catalog — CI stays offline
Keep keys out of the browser for realtime voice Server-side create_realtime_client_secret (and the sec-websocket-protocol helper)
Bring your own credentials / IdP api_key= or a CredentialStore; OAuth URLs are caller-supplied, never hardcoded
Use the rest of the xAI surface Files, embeddings, tokenize, batch, collections, Responses, priority and deferred chat

Not an official xAI package. Domain schemas and the tool loop stay in your app.

How this differs from the official SDK

The official xAI Python SDK (xai_sdk) is the gRPC client xAI ships. xAIkit uses it for live chat and adds a kit around it:

Official SDK xAIkit
Status Official Unofficial kit (this repo / xaikit-py)
Chat live path xai_sdk.Client / protobuf Same SDK under XaiClient; you see JSON dicts, not protobuf
Tests without a key You mock gRPC MockChatProvider + inject_catalog
Model pick You hardcode ids resolve_model("cheapest" | "economy" | "best") per role
Cost None Purpose-tagged UsageMeter + a copied public price table (estimates, not invoices)
REST / WS extras Separate HTTP/WS samples Image, video, STT/TTS, realtime voice, Files, batch, collections on the same client
Auth API key API key, CredentialStore, or caller-supplied OAuth URLs — no User types, no grok.com login

Install stays xaikit-py; import xaikit. You can use both packages in one app.

Install

The import stays xaikit (the PyPI name is xaikit-py because xaikit was too close to an existing explainable-AI project).

uv add xaikit-py
# or: pip install xaikit-py

# From a git tag
uv add "xaikit-py @ git+https://github.com/BrianCLowe/xAIkit@v0.1.3"

# Editable neighbor checkout
uv add --editable ../xAIkit

Problems and feedback

If something breaks, the docs are wrong, or an API is missing, open an issue. Include the package version (python -c "import xaikit; print(xaikit.__version__)"), how you installed (PyPI / git / editable), and a short repro. Redact API keys.

Quick usage

from xaikit import (
    InMemoryUsageSink,
    MockChatProvider,
    UsageMeter,
    XaiClient,
)

meter = UsageMeter(sink=InMemoryUsageSink())
client = XaiClient(
    provider=MockChatProvider(replies="hi"),
    model="grok-4.7",
    usage_meter=meter,
)
resp = client.chat([{"role": "user", "content": "hello"}], purpose="demo.chat")

Async twin (AsyncXaiClient) uses the same method names; REST uses httpx.AsyncClient and live chat uses xai_sdk.AsyncClient. MockChatProvider works with both clients:

import asyncio
from xaikit import AsyncXaiClient, MockChatProvider

async def main() -> None:
    client = AsyncXaiClient(provider=MockChatProvider(replies="hi"), model="grok-4.7")
    resp = await client.chat([{"role": "user", "content": "hello"}])
    print(resp.content)

asyncio.run(main())

When usage_meter is attached, purpose is required. Without a meter, purpose is optional.

Pricing estimates (not invoices)

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.

  • Source: docs.x.ai/developers/pricing (chat also docs.x.ai/docs/models)
  • Last copied into the kit: default_price_table().fetched (also PRICE_TABLE_FETCHED)
  • Refresh: the meter prices a call from the response’s cost_in_usd_ticks when present, then rates on the in-process model list, then a public gap file this repo publishes (at most once per process per 24 hours, and only when a rate is still missing). A failed fetch does not fail the call. The shipped table in pricing.py is the last resort: an exact id, or a prefix whose remainder is empty or starts with - (grok-4.7-latest follows grok-4.7; grok-4.8 does not follow grok-4). Unknown ids leave estimated_usd unset. There is no fetch on import. Operators can still overlay JSON with load_price_table("prices.json").
  • 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.
from xaikit import PRICE_TABLE_FETCHED, PRICE_TABLE_SOURCE_URL, default_price_table

table = default_price_table()
assert table.source_url == PRICE_TABLE_SOURCE_URL
assert table.fetched == PRICE_TABLE_FETCHED
# table.price_for("grok-4.7").input_per_million

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.

Credentials and OAuth

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.

from xaikit import (
    build_oauth_authorize_url,
    exchange_oauth_code,
    oauth_is_configured,
)

# Your app supplies the IdP endpoints — not the kit.
authorize = "https://idp.example.com/authorize"
token = "https://idp.example.com/token"

assert oauth_is_configured(client_id="app-id", client_secret="app-secret")
url = build_oauth_authorize_url(
    client_id="app-id",
    redirect_uri="https://app.example.com/callback",
    state="nonce-1",
    authorize_url=authorize,
)
# tokens = exchange_oauth_code(
#     code,
#     client_id="app-id",
#     client_secret="app-secret",
#     redirect_uri="https://app.example.com/callback",
#     token_url=token,
# )

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 Usage Explorer (a management-key billing API, not OAuth).

Catalog resolve

Callers pass cheapest / economy / best (and optional role=). Chat is the default pool. Pin still wins when pin= is set.

from xaikit import ModelInfo, feature_options, inject_catalog, resolve_model, resolve_model_selection

inject_catalog(
    [
        ModelInfo(id="grok-4.7", capabilities=["chat"], input_per_million=20.0, created=2),
        ModelInfo(id="grok-imagine-image", capabilities=["image"], created=1),
        ModelInfo(id="grok-imagine-image-quality", capabilities=["image"], created=2),
    ]
)

chat_id = resolve_model(intent="economy")  # role="chat" default
image = resolve_model_selection(intent="best", role="image")
video_id = resolve_model(intent="cheapest", role="video")
voice_id = resolve_model(intent="economy", role="voice")
extend_id = resolve_model(intent="best", role="video", need="video_extend")

role is chat | image | video | voice. Offline tests inject fixtures with inject_catalog — do not hit the network.

feature_options(model=) lists extra capabilities for settings UIs (not role tags). No model → Grok 4.7 chat extras, the same set as Grok 4.6+ (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; grok-imagine-video-1.5-lite reports 1080p only. 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). Video best stays on grok-imagine-video-1.5 even when a dated lite id is newer; cheapest can pick the lite SKU when it is on the list.

When model is omitted, an unpinned client resolves chat from the live list (the API key is passed into list_models) and re-resolves on later calls when that cache expires (one hour). BOOTSTRAP_MODEL (grok-4.7) is only the offline fallback. A pin on the client or on the call stays fixed. Image defaults follow the image list the same way. Video and voice stay on their role default until the SDK lists them. Offline with no API key or fixture, list_models injects grok-4.7 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. Newest chat ids compare integer major then minor (grok-5 above grok-4.10 above grok-4.7). grok-4.20 stays older than grok-4.3.

Image generation and edit

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

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

out = client.generate_image(
    "A red cube on a table",
    aspect_ratio="1:1",
    resolution="2k",
    response_format="b64_json",
)
# quality= ("low" | "medium") is grok-imagine-image-2.0 only
edited = client.edit_image(
    "Make it a pencil sketch",
    image_url=out["url"],  # or image_file_id="file-..."
)
# edited["url"] / edited["b64_json"] / edited["file_id"]
# collage = client.edit_image(
#     "Put <IMAGE_0> in the style of <IMAGE_1>",
#     images=["https://example.com/a.png", {"file_id": "file-style"}],
# )

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.

Files

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}.

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
meta = client.upload_file(b"hello", "note.txt", content_type="text/plain")
# meta["id"] is the opaque file_id
# client.get_file(meta["id"])
# client.delete_file(meta["id"])

Uploads larger than 50 MB are rejected before HTTP.

Embeddings

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.

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

out = client.embed(["query: hello", "passage: world"], model="your-embed-sku")
vectors = [row["embedding"] for row in out["data"]]

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.

Tokenizer

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.

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

out = client.tokenize("Hello world")
n = out["count"]
pieces = [row["string"] for row in out["tokens"]]

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.

Batch

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. grok-4.7 is documented as Batch API not supported. 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.

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

job = client.create_batch("nightly-capitals")
client.add_batch_requests(
    job["id"],
    [
        {
            "messages": [{"role": "user", "content": "Capital of France?"}],
            "batch_request_id": "fr",
        }
    ],
)
status = client.get_batch(job["id"])
# status["state"]["num_pending"] / num_success / …
# results = client.list_batch_results(job["id"])

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.

Collections

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.

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.

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])  # plus XAI_MANAGEMENT_KEY in env

coll = client.create_collection("docs")
# coll["id"] / coll["name"]
listed = client.list_collections()
# listed["collections"]
got = client.get_collection(coll["id"])
client.upload_document(coll["id"], "note.txt", b"hello world")
hits = client.search_collections("hello", coll["id"])
# hits["matches"][0]["chunk_content"] / file_id / score
# client.delete_collection(coll["id"])

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.

Video generation

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.

from xaikit import MockChatProvider, VideoInbox, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

inbox = VideoInbox()
started = client.generate_video(
    "A red cube rotating on a table",
    duration=8,
    aspect_ratio="16:9",
    resolution="480p",
    into=inbox,
    wait=False,
)
status = client.poll_video(started["request_id"])
# bytes = client.download_video(status["url"])  # when status == "done"
# status["error"] is set when status is failed / expired (same text wait raises)
# inbox.receipts still has the ticket if a parallel await is cancelled

extend_video(prompt, video_url=..., into=inbox) continues a clip. Generate defaults to grok-imagine-video-1.5; extend remaps 1.5 and grok-imagine-video-1.5-lite to grok-imagine-video (neither can extend). 1080p is kept on 1.5 and 1.5 Lite for text-to-video and image-to-video; reference-to-video and older grok-imagine-video send 720p instead. Lite list rates are $0.02 / $0.03 / $0.14 per second at 480p / 720p / 1080p.

Realtime voice

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.

from xaikit import MockChatProvider, XaiClient, decode_realtime_audio

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

with client.open_realtime_session(
    voice="eve",
    instructions="You are a helpful assistant.",
) as session:
    session.send_text("Hello!")
    for _ in range(8):
        event = session.recv(timeout=30)
        pcm = decode_realtime_audio(event) if isinstance(event, dict) else None
        if pcm:
            break  # app owns playback; this library has no speaker
    # session.send_audio(pcm16_bytes)  # app owns capture; this library has no mic

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).

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).

from xaikit import MockChatProvider, XaiClient, realtime_client_secret_protocol

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])  # server-side only

secret = client.create_realtime_client_secret(expires_after=300)
token = secret["value"]
# Client WS: Authorization: Bearer {token}
protocol = realtime_client_secret_protocol(token)  # "xai-client-secret.{token}"

Streaming speech-to-text

Unary-transcribe over wss://api.x.ai/v1/stt. Send raw PCM bytes (not base64). This is not the realtime voice (STS) socket.

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

pcm16_bytes = bytes(3200)  # 100 ms of 16 kHz s16le PCM — apps own capture
with client.open_stt_session(language="en", interim_results=True) as session:
    session.send_audio(pcm16_bytes)
    session.audio_done()
    for event in session.events():
        if event.get("type") == "transcript.partial":
            print(event.get("text"))
        elif event.get("type") == "transcript.done":
            break

REST file transcription stays on transcribe. Offline tests mock the socket.

Streaming text-to-speech

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.

from xaikit import MockChatProvider, XaiClient, decode_tts_audio

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

with client.open_tts_session(language="en", voice="eve", codec="mp3") as session:
    session.send_text("Hello from streaming TTS.")
    session.text_done()
    for event in session.events():
        chunk = decode_tts_audio(event)
        if chunk:
            pass  # apps own playback
        elif event.get("type") == "audio.done":
            break

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.

audio, content_type = client.synthesize_speech(
    "Hello from REST TTS.",
    voice_id="eve",
    codec="wav",
    sample_rate=24000,
    speed=1.0,
)

Offline tests mock the socket.

List built-in TTS voices (not team-scoped custom clones):

voices = client.list_tts_voices()
# voices[0]["voice_id"] / ["name"] / ["language"]
# client.get_tts_voice("eve")

Streaming

for chunk in client.chat_stream(
    [{"role": "user", "content": "hello"}],
    purpose="demo.stream",
):
    print(chunk.delta, end="", flush=True)

Tools, vision, and structured JSON

The kit wraps xAI chat extras as JSON dicts. It does not run tools — the app owns the loop.

from xaikit import MockChatProvider, XaiClient

weather_tool = {
    "name": "get_weather",
    "description": "Get the weather for a city.",
    "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}
client = XaiClient(
    provider=MockChatProvider(
        replies=[
            "a cube",
            {
                "tool_calls": [
                    {"id": "call_1", "name": "get_weather", "arguments": {"city": "NYC"}},
                ],
            },
            {"title": "blue"},
        ]
    ),
    model="grok-4.7",
)

# Vision: content may be a string or a list of parts
client.chat(
    [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "What is in this image?"},
                {"type": "image_url", "url": "https://example.com/cube.png"},
            ],
        }
    ]
)

# Tools: defs in, tool_calls out (arguments are parsed JSON, typically a dict)
resp = client.chat(
    [{"role": "user", "content": "Weather in NYC?"}],
    tools=[weather_tool],
    tool_choice="auto",
)
# resp.tool_calls → [{"id": "call_1", "name": "get_weather", "arguments": {"city": "NYC"}}]
# App runs the function, then sends the assistant turn + tool result:
# client.chat([
#     {"role": "user", "content": "Weather in NYC?"},
#     {"role": "assistant", "content": "", "tool_calls": resp.tool_calls},
#     {"role": "tool", "content": "72F", "tool_call_id": resp.tool_calls[0]["id"]},
# ], tools=[weather_tool])

# Native structured outputs (fence-stripping remains the fallback)
schema = {
    "type": "object",
    "properties": {"title": {"type": "string"}},
    "required": ["title"],
}
data = client.chat_json("Name a color", schema=schema)

Responses API (built-in tools)

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=.

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

out = client.create_response(
    "What is 101*3?",
    tools=[{"type": "code_interpreter"}],  # omit tools= for text-only
)
# out["id"] / out["output"] / out["usage"]
# client.get_response(out["id"])

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.

Priority processing and deferred chat

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.

Deferred completions are a separate REST pair — not a second return type on chat:

from xaikit import MockChatProvider, XaiClient

client = XaiClient(provider=MockChatProvider(), api_key="test-key")
# Live: XaiClient(api_key=os.environ["XAI_API_KEY"])

ticket = client.create_deferred_chat([{"role": "user", "content": "126/3=?"}])
# ticket["request_id"]
# result = client.get_deferred_chat(ticket["request_id"])
# result["status"] is "pending" (HTTP 202) or "complete" (HTTP 200 + completion fields)

Create and pending get meter modality="chat" without tokens. A complete get may record usage tokens. No invented USD.

Opt-in dev completion traces (default off)

from xaikit import CompletionTracer, InMemoryTraceSink, MockChatProvider, XaiClient

tracer = CompletionTracer(sink=InMemoryTraceSink())
client = XaiClient(
    provider=MockChatProvider(replies="hi"),
    model="grok-4.7",
    completion_tracer=tracer,
)
client.chat([{"role": "user", "content": "hello"}])

Optional gap log (companion — default off)

uv run python -m xaikit.gaps --path ./gaps.jsonl
# or: xaikit-gaps --path ./gaps.jsonl --kind capability_gap

HTTP mounts (examples/docs only)

Thin FastAPI samples under examples/ — not required package surface.

License

MIT

Contributor / agent docs (not part of the installed package): docs/Master_Index.md.

Metadata

Release files for xaikit-py 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xaikit-py 0.1.3
File Size Uploaded
xaikit_py-0.1.3.tar.gz 155.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xaikit-py 0.1.3
File Interpreter ABI Platform
xaikit_py-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 266.2 kB

Release files / xaikit_py-0.1.3.tar.gz

Download URL xaikit_py-0.1.3.tar.gz
Size 155.9 kB
Tags Source
SHA-256 checksum
How to use checksums
47deab636ea85d2fabb39562159a346d497c2f33932fc5ef385b71e0a1b76c0a
BLAKE2b-256 checksum
How to use checksums
3aa11eaa2150f3fff22f38e248e31dce1b7314be61a7751f8a103ba566d0b90d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / xaikit_py-0.1.3-py3-none-any.whl

Download URL xaikit_py-0.1.3-py3-none-any.whl
Size 110.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
be838710009a553a312f20bc0281fc1ef2fd554c245dccda3393e299017f0648
BLAKE2b-256 checksum
How to use checksums
855f58bef9c8f1b80377964ed5de03e51af68fd08151f0ba4d59c583db733144
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page