model SDK built by the 9th district at Tooig
Project description
nineth
nineth is the Python SDK for the 1984 model API.
This document is caller-facing and SDK-focused. It is designed as a cookbook: start simple, then layer in advanced runtime controls.
Install
pip install nineth
export NINETH_API_KEY="your-api-key"
Optional env vars:
export NINETH_BASE_URL="https://weirdpablo--rooster-api.modal.run"
export NINETH_DEFAULT_MODEL="1984-m3-0317"
Public SDK Surface
Most apps only need these public objects:
- NinethClient (sync)
- AsyncNinethClient (async)
- client.health()
- client.model.request(...)
- AVAILABLE_MODELS
- NinethAPIError
Models
Current SDK model list:
- 1984-m0-brute
- 1984-m0-sm
- 1984-m1-unified
- 1984-m2-light
- 1984-m2-preview
- 1984-m3-0317
- 1984-m3-0404
- 1984-m3-0421
- 1984-m3-0424
- 1984-c0-0427
1) Create a Client Once and Reuse It
Sync client
import httpx
from nineth import NinethClient
client = NinethClient(
base_url="https://weirdpablo--rooster-api.modal.run",
api_key="nt_live_xxx",
default_model="1984-m3-0317",
timeout=httpx.Timeout(300.0, connect=10.0),
stream_timeout=httpx.Timeout(connect=10.0, read=None, write=60.0, pool=60.0),
headers={"X-App-Name": "my-app"},
)
# use client.model.request(...) repeatedly
client.close()
Async client
import asyncio
from nineth import AsyncNinethClient
async def main() -> None:
async with AsyncNinethClient(
api_key="nt_live_xxx",
default_model="1984-m3-0317",
) as client:
response = await client.model.request("Say hello in one short sentence.")
print(response["final_response"])
asyncio.run(main())
2) Health Check
from nineth import NinethClient
with NinethClient() as client:
print(client.health())
Typical response:
{
"status": "ok",
"timestamp": "2026-05-24T12:00:00+00:00"
}
3) Basic Buffered Request
from nineth import NinethClient
with NinethClient(default_model="1984-m3-0317") as client:
response = client.model.request("Give me a concise BTC market brief.")
print(response["final_response"])
Typical response shape:
{
"final_response": "BTC is range-bound with elevated event risk.",
"iterations": 1,
"thinking": [],
"service_calls": [],
"service_responses": [],
"artifacts": [],
"usage": {
"prompt_tokens": 123,
"completion_tokens": 41,
"total_tokens": 164
},
"events": []
}
4) Streaming Request
from nineth import NinethClient
with NinethClient(default_model="1984-m3-0317") as client:
for event in client.model.request("Summarize oil market headlines.", stream=True):
if event["type"] == "model_delta":
print(event["data"]["text"], end="", flush=True)
elif event["type"] == "result":
print("\n\nFinal:", event["data"]["final_response"])
Typical stream event sequence:
{"type": "accepted", "data": {"ok": true}}
{"type": "model_delta", "data": {"text": "Crude prices"}}
{"type": "model_delta", "data": {"text": " rose on..."}}
{"type": "result", "data": {"final_response": "Crude prices rose on...", "iterations": 1}}
5) Generation Controls
Use these to tune model behavior per request:
- reasoning: disabled | low | medium | high
- show_reasoning: bool
- temperature
- top_p, min_p, top_k
- repetition_penalty, presence_penalty, frequency_penalty
- seed
Example:
response = client.model.request(
"Compare two risk scenarios in 5 bullets.",
reasoning="high",
show_reasoning=False,
temperature=0.2,
top_p=0.9,
min_p=0.01,
top_k=40,
repetition_penalty=1.05,
presence_penalty=0.1,
frequency_penalty=0.1,
seed=1337,
)
6) Loop Controls
- max_iterations caps model turns.
- continuous controls idle-loop persistence.
response = client.model.request(
"Monitor and update only when new data arrives.",
max_iterations=20,
continuous=True,
)
7) Cache and Session Reuse
Use cache=True to persist and receive session_id (mapped from process_id).
first = client.model.request(
"Remember my preferred output format: CSV.",
cache=True,
)
sid = first["session_id"]
second = client.model.request(
"Now summarize open tasks.",
cache=True,
session_id=sid,
)
Important:
- session_id requires cache=True unless resuming callback with client_service_results.
8) policy vs guardrail
- policy: caller runtime policy text for the request.
- guardrail: ADAM extension text for default SDK/API ingress path.
response = client.model.request(
"Draft an operations note.",
policy="Keep response under 120 words and use plain language.",
guardrail="Never include secrets or credentials.",
)
9) base_system Provider Path
base_system defaults to True.
Set False only when you explicitly want runtime default provider fallback.
response = client.model.request(
"Run with runtime provider fallback.",
base_system=False,
)
10) Built-in Services with default_service
default_service is the main built-in service switch:
- False: built-ins disabled
- True: built-ins enabled
- list[str]: built-in allowlist
It also supports alias groups in SDK payload shaping, including browser, knowledge, computer, workspace, voice, trading, and shop.
response = client.model.request(
"Search for the latest shipping regulations and summarize.",
default_service=["browser", "search_knowledge"],
)
11) include_service (Local and Callback)
include_service supports three practical patterns:
- Local schema.py path (SDK executes locally)
- Inline schema dict list
- Callback block with URL and schema list
A) Local schema path
response = client.model.request(
"Get weather for Lagos using local service.",
include_service=["./services/weather/schema.py"],
)
B) Inline schema
weather_schema = {
"name": "get_weather",
"description": "Return weather forecast.",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
"additionalProperties": False,
},
}
response = client.model.request(
"Use inline service schema for weather.",
include_service=[weather_schema],
)
C) Callback URL block
response = client.model.request(
"Call my hosted weather callback.",
include_service={
"callback": {"url": "https://my-app.example.com/include-service-callback"},
"schema": [weather_schema],
},
)
When callback flow pauses, server can return awaiting_client_services with pending_client_calls.
12) callback_url as Global Callback
callback_url can be reused across callback-capable surfaces and can be inherited by inbound email templates that omit template url.
response = client.model.request(
"Register inbound mailbox setup.",
callback_url="https://my-app.example.com/rooster-callback",
include_service=[weather_schema],
)
13) Manual Callback Resume with client_service_results
For caller-managed continuation:
paused = client.model.request(
"Pause on client services.",
include_service=[weather_schema],
)
if paused.get("status") == "awaiting_client_services":
resumed = client.model.request(
"Resume after local service execution.",
include_service=[weather_schema],
client_service_results=[
{
"call_id": "client_1_1",
"service_name": "get_weather",
"success": True,
"result": {"location": "Lagos", "forecast": "sunny"},
}
],
session_id=paused.get("process_id"),
)
14) response_format and compute
response_format="json" parses final_response when valid JSON and preserves original raw_response.
response = client.model.request(
"Return JSON with keys trend and confidence.",
response_format="json",
compute=True,
)
print(response["final_response"]) # parsed object if valid JSON
print(response.get("raw_response"))
print(response.get("compute"))
Typical parsed response:
{
"final_response": {
"trend": "neutral",
"confidence": 0.64
},
"raw_response": "{\"trend\":\"neutral\",\"confidence\":0.64}",
"compute": 241
}
15) Messaging Configuration
messaging attaches transport defaults for email and telegram.
response = client.model.request(
"Send a concise status update.",
messaging={
"email": {
"address": "ops@resident.tooig.com",
"name": "Ops Bot",
"instruction": "Always ask for ticket id before escalation.",
"reasoning_effort": "high",
},
"telegram": {
"botId": "bot-1",
"chatId": "12345678",
"reasoning_effort": "disabled",
},
},
)
Auto-enable behavior:
- telegram messaging enables telegram send/edit services.
- email messaging enables email send services when outbound-capable.
- inbound-only template registration does not auto-enable email send services.
16) Template-Scoped Email Payloads
Canonical message payload location:
- messaging.email.templates[*].message
- messaging.email.templates[*].messages
Inbound template example:
response = client.model.request(
"Register inbound mailbox template.",
messaging={
"email": {
"address": "agent@resident.tooig.com",
"instruction": "Handle review requests.",
"templates": [
{
"type": "inbound",
"name": "review_request",
"standalone": True,
"messages": [
{
"from": "applicant@example.com",
"to": "agent@resident.tooig.com",
"subject": "Application follow-up",
"body": "Could you share my status?",
}
],
}
],
}
},
callback_url="https://my-app.example.com/inbound-callback",
)
Outbound template example:
response = client.model.request(
"Send outbound applicant decision.",
messaging={
"email": {
"address": "agent@resident.tooig.com",
"templates": [
{
"type": "outbound",
"name": "decision_email",
"messages": [
{
"to": "applicant@example.com",
"subject": "Application Decision",
"body": "Thanks for your time. We would like to proceed.",
"cc": ["hr@example.com"],
"bcc": ["audit@example.com"],
}
],
}
],
}
},
)
17) Audio and Images
response = client.model.request(
"Transcribe audio and summarize key action items.",
audio=[
{
"data": "<base64-audio>",
"mime_type": "audio/mpeg",
"filename": "meeting.mp3",
}
],
)
response = client.model.request(
"Describe this chart.",
images=["<base64-image>"],
)
18) Streaming with Service Progress
When services are called, stream can include synthetic progress lines and service call/response events.
for event in client.model.request("Research and summarize.", stream=True, default_service=["browser"]):
et = event["type"]
if et == "model_delta":
print(event["data"]["text"], end="")
elif et == "service_call":
print("\nservice call:", event["data"])
elif et == "service_response":
print("\nservice response:", event["data"])
19) Error Handling
All API and stream errors are raised as NinethAPIError.
from nineth import NinethAPIError
try:
response = client.model.request("Run protected operation.")
except NinethAPIError as exc:
print("nineth error:", str(exc))
SDK behavior includes sanitizing provider/URL fragments from surfaced errors and masking some model subscription-availability patterns.
20) End-to-End Example: Cached Session + Services + JSON Output
from nineth import NinethClient
weather_schema = {
"name": "get_weather",
"description": "Return weather forecast.",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
"additionalProperties": False,
},
}
with NinethClient(api_key="nt_live_xxx", default_model="1984-m3-0421") as client:
step1 = client.model.request(
"Create a daily logistics summary format and remember it.",
cache=True,
default_service=["browser"],
)
sid = step1.get("session_id")
step2 = client.model.request(
"Use today weather in Lagos and return JSON with fields summary and risk_level.",
cache=True,
session_id=sid,
include_service=[weather_schema],
response_format="json",
compute=True,
reasoning="medium",
)
print(step2["final_response"])
print("tokens:", step2.get("compute"))
Troubleshooting
Common issues and fixes:
-
ValueError: Authentication required Set api_key or NINETH_API_KEY.
-
ValueError: A model is required Pass model=... or set default_model/NINETH_DEFAULT_MODEL.
-
ValueError: session_id requires cache=True When reusing session_id, set cache=True.
-
include_service callback conflicts Do not define a user service named request_include_service_interlude when callback URL mode is used.
-
Stream appears to stop at awaiting_client_services Resume manually with client_service_results, or use callback URL mode to auto-resume.
Quick Reference: request(...) Arguments
Task identity:
- task_input
- model
Execution mode:
- stream
- max_iterations
- continuous
Generation controls:
- reasoning
- show_reasoning
- temperature
- top_p
- min_p
- top_k
- repetition_penalty
- presence_penalty
- frequency_penalty
- seed
Runtime controls:
- policy
- guardrail
- base_system
- default_service
- include_service
- callback_url
- client_service_results
Continuity:
- cache
- session_id
Inputs:
- images
- audio
Output shaping:
- response_format
- compute
- verbose
- debug (legacy alias)
Transport:
- messaging
Legacy alias compatibility:
- policy also accepts system_prompt
- verbose may be influenced by debug
Final Note
If you are integrating nineth in production:
- Create one long-lived client per process.
- Reuse cached sessions explicitly when continuity matters.
- Keep default_service strict and intentional.
- Treat include_service schemas as request-scoped contracts.
- Prefer callback_url mode for distributed service execution.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nineth-0.6.48.tar.gz.
File metadata
- Download URL: nineth-0.6.48.tar.gz
- Upload date:
- Size: 29.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91fd454889080a4c68c87bf5081d2fdeeb810dbf71ed5eebe9c14fb9a966d673
|
|
| MD5 |
5f344dcafedd21429b66fd9726b8dffc
|
|
| BLAKE2b-256 |
905350924a0046c362b258c32005f835eb0bc160e3a257f0e2ad3f595e956722
|
File details
Details for the file nineth-0.6.48-py3-none-any.whl.
File metadata
- Download URL: nineth-0.6.48-py3-none-any.whl
- Upload date:
- Size: 30.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67f3f08a3d8d228877d1e89a479263fb81e526df77bd31d3678be5feea8b9ce1
|
|
| MD5 |
b529b5b99a211feca4027ea462114d24
|
|
| BLAKE2b-256 |
b63947c1ee51abaf731351335c16503ffae5fa0195627b6d62ca1dc6cea2f5ce
|