SDK
Python SDK
Use the Stimulir Python SDK when an application, backend, or agent needs to manage prompts, data assets, Lab evals, adapter and projector tuning, tenant storage, or capability execution without shelling out to the CLI.
Install
The SDK ships with the stimulir Python package (requires Python 3.11+). Add it to the application or backend environment that will call Stimulir with either uv or pip:
uv add stimulirpip install stimulirIf you already manage a virtual environment directly, install or upgrade into that environment:
uv pip install -U stimulirpip install -U stimulirRealtime/websocket features pull in an optional extra:
uv add "stimulir[realtime]"pip install "stimulir[realtime]"uv tool install stimulir installs the terminal CLI as an isolated command-line tool. Use it for shell workflows, not as the dependency path for application code that imports the SDK. See CLI Installation.
Get an API key
The SDK authenticates with a workspace-scoped hyb_* inference key generated from the Console, this is a different credential from the personal stim_cli_ token the stimulir CLI uses to log in.
- Sign in (or create an account) at
console.stimulir.com. - Click on your User profile.
- Navigate to Settings.
- Select Adaptive inference to manage your keys.
- Copy the plaintext key immediately, it is shown only once. Store it in a secret manager.
See API Keys for the full lifecycle (rotation, revocation, limits) and the equivalent REST/CLI paths.
Configure
The key carries its workspace and platform scope, so application code does not need to pass a workspace id for normal prompt, data, eval, or inference calls.
STIMULIR_API_BASE=https://api.stimulir.com
STIMULIR_API_KEY=hyb_...
STIMULIR_PROJECT_ID=...from decouple import config
from stimulir import StimulirClient
client = StimulirClient(
api_base=config("STIMULIR_API_BASE"),
api_key=config("STIMULIR_API_KEY"),
project_id=config("STIMULIR_PROJECT_ID"),
)Prompts
The basic agent flow begins with defining your prompts. You can either fetch managed templates from the UI or construct raw prompts locally.
Method A: Prompts from UI
First, create your prompt template in the console:
- Navigate to your active workspace (e.g., Engineering).
- Click on Prompts in the sidebar menu.
- Click New Prompt, assign a unique Key (e.g.
customer-chat-agent), and write your template using variables like{category}.
Then, fetch and render the prompt via the SDK using its key and your local variables:
prompt_ui = client.prompt('customer-chat-agent', {'category': 'support', 'name': 'Alice'})Method B: Raw Prompts
Or simply construct a raw string locally within your codebase:
profession = "Software Engineer"
raw_prompt = f"Recommend the top 5 movies for someone who works as a {profession}."Models
The model string decides the route. Managed inference runs on Stimulir's own capacity with no key, send the bare name, or prefix with stimulir/ to pin managed even when you hold a BYOK credential. A provider prefix routes to your own BYOK credential instead. Prices are reference rates in USD per 1M tokens (input · output); see Usage & Billing for current pricing.
Managed inference, no key
| Model | Served by | $/1M in · out |
|---|---|---|
stimulir/claude-opus-4-6 | Claude Opus 4.6 frontier, platform Bedrock | 15 · 75 |
stimulir/claude-sonnet-4-6 | Claude Sonnet 4.6 frontier, platform Bedrock | 3 · 15 |
moonshotai/Kimi-K3 | Kimi K3, MI engine | MI rate |
moonshotai/Kimi-K2.6 | Kimi K2.6 (vision), MI engine | MI rate |
moonshotai/Kimi-K2.7-Code | Kimi K2.7 Code, MI engine | MI rate |
MiniMaxAI/MiniMax-M3 | MiniMax M3, MI engine | MI rate |
MiniMaxAI/MiniMax-M2.5 | MiniMax M2.5, MI engine | MI rate |
Qwen/Qwen2.5-VL-72B-Instruct | Qwen2.5-VL 72B (vision), MI engine | MI rate |
stimulir/fusion | Panel + judge over the open models | sum of parts |
stimulir/fusion-max | Panel + judge over the frontier models | sum of parts |
Managed frontier is Claude today (claude-opus-4-6, claude-sonnet-4-6). Open models are managed on their bare vendor name; the stimulir/ prefix is optional there but forces managed on any name. See Fusion for the recipe presets.
BYOK, bring your key
Prefix a model with your provider to route through your own credential. BYOK is not an allowlist, any model your provider serves works; the tables below are the metered reference rates.
openai/
| Model | $/1M in · out |
|---|---|
gpt-4o | 2.5 · 10 |
gpt-4o-mini | 0.15 · 0.6 |
gpt-4o-realtime | 5 · 20 |
o1 | 15 · 60 |
o1-mini | 3 · 12 |
o3-mini | 3 · 12 |
anthropic/, Claude, direct
| Model | $/1M in · out |
|---|---|
claude-opus-4 | 15 · 75 |
claude-sonnet-4.6 | 3 · 15 |
claude-sonnet-4.5 | 3 · 15 |
claude-haiku-4.5 | 0.8 · 4 |
bedrock/ or vertex/, Claude, via cloud
| Model | $/1M in · out |
|---|---|
claude-opus-4-8 | 15 · 75 |
claude-opus-4-7 | 15 · 75 |
claude-opus-4-6 | 15 · 75 |
claude-sonnet-4-6 | 3 · 15 |
claude-3-7-sonnet | 3 · 15 |
claude-3-5-sonnet | 3 · 15 |
claude-3-5-haiku | 0.8 · 4 |
google_gemini/ or vertex/, Gemini
| Model | $/1M in · out |
|---|---|
gemini-3.5-flash | 1.5 · 9 |
gemini-3.1-pro-preview | 2 · 12 |
gemini-3.1-flash-lite | 0.25 · 1.5 |
gemini-3-pro-preview | 2 · 12 |
gemini-3-flash-preview | 0.5 · 3 |
gemini-2.5-pro | 1.25 · 5 |
gemini-2.5-flash | 0.075 · 0.3 |
gemini-2.5-flash-lite | 0.05 · 0.2 |
gemini-2.5-flash-native-audio-preview-12-2025 | 0.5 · 2 |
mistral/
| Model | $/1M in · out |
|---|---|
mistral-large-2.5 | 2 · 6 |
mistral-medium-3.1 | 0.4 · 2 |
mistral-small-3.2 | 0.2 · 0.6 |
codestral-2.5 | 0.3 · 0.9 |
pixtral-large-2.5 | 2 · 6 |
Vision is per-request: send image parts to any chat model on any lane. The Gemini, GPT-4o, and Claude families accept images, plus pixtral-large-2.5, moonshotai/Kimi-K2.6, and Qwen/Qwen2.5-VL-72B-Instruct (the managed hybrie-vision tier). Realtime voice runs on Gemini native-audio,gpt-realtime, and Qwen2.5-Omni, see Realtime Voice. stimulir/ is the only managed prefix; see Deployment Modes for BYOK vs Managed vs BYOC.
Inference (chat completions)
Execute the prompt against the agent. Before execution, the system checks your Bring Your Own Key (BYOK) settings. If a matching key is found for the requested model, it uses it; otherwise, it falls back to the Stimulir system LLM and bills accordingly.
response = client.agent(
prompt=raw_prompt, # You can pass the raw_prompt (Method B) or the prompt_ui (Method A)
role='user',
tags=["call_center"],
model="claude-opus-4-6"
)
if response.error:
print(f"Error: {response.error}")
else:
print(response.content)Response Payload
The client.agent() call returns an AgentResponse object containing the following properties:
content: The text response from the model.error: A string describing any API error that occurred (orNoneif successful).status: The execution status (e.g.,"success","error").conversation_id: The unique ID linking this interaction.cost: The calculated cost of the request.token_usage: A dictionary containing token counts (e.g.prompt_tokens,completion_tokens).
Multi-turn conversations (system prompt + history)
client.agent() is a one-shot helper, a single prompt and role. To send a system prompt together with full conversation history in one call, send the OpenAI-compatible messages array through the same client with client.request():
resp = client.request("POST", "/api/v1/inference/chat/completions", json_body={
"model": "stimulir/claude-sonnet-4-6",
"messages": [
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "earlier question"},
{"role": "assistant", "content": "earlier answer"},
{"role": "user", "content": "follow-up"}, # history just stacks here
],
"max_tokens": 800,
})
print(resp["choices"][0]["message"]["content"])Set max_tokens generously (≥800): several managed models are reasoning models, and a tight cap can be consumed by reasoning before any visible output is emitted. Any OpenAI-compatible client pointed at https://api.stimulir.com/api/v1/inference accepts the same body.
Advanced Capabilities
Fusion
client.fusion wraps the panel + judge virtual models with panel/judge overrides. Multi-model fusion completions are supported via the gateway's virtual fusion model:
response = client.fusion.complete(
prompt="Analyze this system architecture for single points of failure.",
panel=["moonshotai/Kimi-K3", "moonshotai/Kimi-K2.6"],
judge="claude-opus-4-8",
max_tier=True
)
print(response["choices"][0]["message"]["content"])Realtime voice
Realtime websocket-based agent interactions are supported by adding the stimulir[realtime] extra dependency. Behind the realtime extra, RealtimeClient speaks the gateway's voice WebSocket with typed events, auth, project attribution, and turn-detection control:
from stimulir.realtime import AudioDelta, RealtimeClient, ResponseDone
client = RealtimeClient(instructions="You are a concise voice assistant.")
async with client.connect() as conn:
await conn.setup()
await conn.send_audio(pcm16_chunk)
await conn.commit()
async for event in conn.events():
if isinstance(event, AudioDelta):
play(event.pcm16)
elif isinstance(event, ResponseDone):
breakFull protocol details on Realtime Voice.
Prompts
Fetch prompt versions by key, label, or exact version; create new versions; update metadata; archive old versions; and persist prompt lineage alongside application sessions.
from stimulir import StimulirClient
client = StimulirClient()
prompt = client.prompts.get("customer.chat.agent", label="prod")
session_metadata = {
**prompt.lineage,
"model": "Qwen/Qwen2.5-VL-72B-Instruct",
}
# Use prompt.content in your runtime, then store session_metadata with the result.client.prompts.create_version(
"customer.chat.agent",
content=open("prompts/customer_chat_agent.md").read(),
label="prod",
prompt_type="system",
change_notes="Seed current Customer chat agent prompt",
)
client.prompts.patch_metadata(
"customer.chat.agent",
3,
change_notes="Tightened interview scoring rubric",
)
client.prompts.label("customer.chat.agent", 3, "prod")
client.prompts.archive("customer.chat.agent", 2)Tags
Tag calls when the SDK is embedded in more than one place in the same project, separate services, background jobs, or pipeline stages, so the prompts, data assets, and eval runs each one creates stay attributable to their source.
Client-level tags
Pass tags when constructing the client. These identify the calling implementation and are attached to everything that client creates:
# checkout-service
checkout_client = StimulirClient(tags=["checkout-service", "prod"])
# returns-service
returns_client = StimulirClient(tags=["returns-service", "prod"])Call-level tags
Individual calls can add tags for context specific to that call, a retry, a low-confidence result, an experiment name. Supported on client.prompts.create_version, client.data_assets.create, client.data_assets.from_trace, and client.lab_evals.create_run:
checkout_client.data_assets.from_trace(
"<trace-id>",
source="agent",
tags=["retried", "low-confidence"],
)How merging works
Client tags and call tags are combined automatically, client tags come first (they identify the implementation), followed by call-site tags, with duplicates removed while order is preserved:
checkout_client = StimulirClient(tags=["checkout-service", "prod"])
checkout_client.data_assets.create(
name="checkout-interaction-42",
kind="trace",
tags=["low-confidence"],
)
# -> stored tags: ["checkout-service", "prod", "low-confidence"]Data assets
Use data assets as the hub between traces, uploads, cleaning jobs, snapshots, SFT datasets, preference data, and eval sets.
asset = client.data_assets.upload(
"datasets/customer_agent_eval.jsonl",
name="Customer Agent eval seed",
target="eval",
)
client.data_assets.patch(asset["id"], description="Held-out interview eval cases")
client.data_assets.stage(asset["id"], "lab", status="ready")
trace_asset = client.data_assets.from_trace(
"<trace-id>",
source="agent",
target="sft",
name="Cleaned prompt trace",
)
snapshot = client.data_assets.snapshot(trace_asset["id"])client.data_assets.bulk_stage(
["<asset-id-1>", "<asset-id-2>"],
stage="lab",
target="eval",
lineage_event={"job": "redact-normalize-label"},
)
client.data_assets.unstage("<asset-id>")Lab evals
Lab eval runs compare prompt versions, staged data assets, inference endpoints, adapters, and policies before promotion.
run = client.lab_evals.create_run(
name="Customer Agent prompt regression",
data_asset_id=snapshot["id"],
prompt_ref="customer.chat.agent:prod",
model="hybrie-runtime-default",
execute=True,
)
report = client.lab_evals.get_run(run["id"])See Evaluation for the durable eval model and CLI equivalents.
One-shot runtime scoring lives on the same client, it scores a checkpoint or adapter directly on the runtime rather than persisting a durable run:
client.lab_evals.score_niah(family="qwen3-4b", checkpoint_dir="/ckpt/d2l")
client.lab_evals.score_adapter(family="qwen3-4b", adapter_dir="/ckpt/adapter")
client.lab_evals.score_rl(family="qwen3-4b", environment="niah", policy="peft-lora", checkpoint_dir="/ckpt/rl")Lab tuning
Drive all three training routes, manage jobs, and hot-swap adapters from code, the same control plane the CLI lab commands use. Calls route through the console proxy and resolve the hyb_* key's workspace server-side, so no workspace id is needed. Pass runtime_endpoint to the client to bypass the proxy and hit a HybrIE runtime directly (the SDK equivalent of the CLI's --endpoint).
client = StimulirClient() # or StimulirClient(runtime_endpoint="http://localhost:8011")
# Route 1, PEFT LoRA (the #1 standard route: explicit rank/alpha)
sft = client.lab_train.sft(family="qwen3-4b", lora_rank=16, examples=200, epochs=3)
rl = client.lab_train.rl(family="qwen3-4b", policy="peft-lora", lora_rank=16, prompts=64)
# Route 2, Doc-to-LoRA (hypernetwork context internalisation)
d2l = client.lab_train.d2l(family="qwen3-4b", examples=500, epochs=3)
# Track training jobs
client.lab_jobs.list()
job = client.lab_jobs.get(sft["job_id"])
client.lab_jobs.cancel(rl["job_id"])# Hot-swap LoRA adapters into the runtime for inference
client.lab_adapters.list()
client.lab_adapters.load("<adapter-id>")
client.lab_adapters.unload("<adapter-id>")
# Route 3, multimodal projector tuning (frozen encoder -> projector -> frozen LLM).
# stage1 trains the projector only; stage1+2 also jointly tunes a Stage-2 LoRA.
run = client.lab_projectors.create(
modality="image",
stages="stage1+2",
lora_rank=8,
lora_alpha=16,
data_asset_id="<paired-multimodal-asset-id>",
name="vision-projector",
)
client.lab_projectors.get(run["run"]["id"])
client.lab_projectors.list(modality="image")The end-to-end loop: curate a data asset snapshot, train (adapter or projector), score it with lab_evals, then lab_adapters.load the result to serve it through the inference API.
Capabilities
Agents can call registered Stimulir capabilities through the SDK when they need the same typed backend path that the console uses.
result = client.capabilities.execute(
"engineering_usage_summary",
scope={
"mode": "chat",
"modeContext": "workspace",
"opsWorkspace": "engineering_ops",
},
args={"window": "month"},
confirmed=True,
)Storage
Objects produced by skills and dedicated tasks, in per-tenant compute storage. Pass the workspace (business-profile) id the hyb_* key belongs to; it scopes every call. upload is the on-ramp for a fully SDK-driven pipeline: upload → run → read output.
storage_key = client.storage.upload(
open("clip.mp4", "rb"),
workspace_id="<workspace-id>",
filename="clip.mp4",
)
objects = client.storage.list(workspace_id="<workspace-id>")
objects = client.storage.list(workspace_id="<workspace-id>", task_id="<task-id>")
folders = client.storage.folders(workspace_id="<workspace-id>")
link = client.storage.access_url("<object-id>", expires_in_seconds=900, download=True)
print(link["access_url"])
client.storage.delete("<object-id>")
# Close the loop: fetch an object by its storage key (what run/pipe return),
# not its object id. workspace_id defaults from your key's config.
info = client.storage.get_by_key(output_key, workspace_id="<workspace-id>")
data = client.storage.read_by_key(output_key) # -> bytesManaged skills
Import a repo's skills into a workspace's sandbox, pinned to the commit.
candidates = client.skills.discover("tosi-n", "agentic-frame", workspace_id="<workspace-id>", ref="main")
imported = client.skills.import_(
"tosi-n", "agentic-frame", ["skills/evidence-clip"],
workspace_id="<workspace-id>", ref="main",
)
installed = client.skills.list(workspace_id="<workspace-id>")Sandbox
Run one imported skill against one stored input in an isolated sandbox, or pipe several so each step's output_key feeds the next step's input — a skill's output key is already a valid input key.
result = client.sandbox.run(
"evidence-clip",
workspace_id="<workspace-id>",
input_key=storage_key,
params={"start": 1, "end": 4},
)
print(result["status"], result["output_key"])
# Pipe several skills, threads each output_key into the next input_key.
# Short-circuits on the first step that does not complete (SandboxPipeError).
# Steps: a bare slug, a {"skill", "params"} dict, or a (skill, params) tuple.
results = client.sandbox.pipe(
["privacy-layer", {"skill": "capture-traces", "params": {"window": "7d"}}],
input_key=storage_key,
workspace_id="<workspace-id>", # optional, defaults from your key's config
)
final_key = results[-1]["output_key"]
runs = client.sandbox.list(workspace_id="<workspace-id>")Codex config helpers
The SDK exposes the same renderer used by stimulir launch codex-app for applications that need to inspect or distribute a Stimulir provider block without shelling out.
from stimulir import CodexProviderConfig, render_codex_config_snippet
snippet = render_codex_config_snippet(
CodexProviderConfig(
model="moonshotai/Kimi-K2.7-Code",
base_url="https://api.staging.stimulir.com/api/v1/inference",
project_id="<project-id>",
auth_command="stimulir",
auth_args=("codex", "token", "--api-base", "https://api.staging.stimulir.com"),
)
)
print(snippet)SDK surface
Client
StimulirClient(api_base=None, api_key=None, project_id=None, runtime_endpoint=None, timeout=..., tags=None),tagsis merged into every prompt version, data asset, and eval run this client creates (see Tags);runtime_endpointpoints Lab tuning/scoring at a HybrIE runtime directly instead of the console proxy.client.request(method, path, params=None, json_body=None)for low-level platform API calls.
Subclients
client.prompts,list,get,versions,create_version(acceptstags),patch_metadata,label,archive.client.data_assets,list,create(acceptstags),upload,from_trace(acceptstags),patch,stage,unstage,bulk_stage,snapshot.client.lab_evals,list_runs,get_run,create_run(acceptstags),execute_run, plus runtime scoringscore_niah,score_adapter,score_rl.client.lab_train,d2l,sft,rl(the three training routes).client.lab_jobs,list,get,cancel.client.lab_adapters,list,get,load,unload.client.lab_projectors,list,get,create,delete.client.storage,upload,list,folders,access_url,delete,get_by_key,read_by_key.client.skills,discover,import_,list.client.sandbox,run,pipe,list.client.fusion,complete(panel/judge overrides). See Stimulir Fusion.client.models, list gateway-routable model ids.client.privacy, privacy/redaction controls.client.capabilities,execute.stimulir.realtime.RealtimeClient(via therealtimeextra), typed voice WebSocket client. See Realtime Voice.CodexProviderConfigandrender_codex_config_snippet, render Codex App provider config for Stimulir.