Chat completions: your first Muse Spark call
What you'll build: a working chat completion against Muse Spark with the OpenAI Python SDK. Then you add instruction messages, temperature control, and multi-turn conversations.
| Section | API fundamentals |
| Capabilities | Text generation |
| Time to complete | ~10 min |
| Model | muse-spark-1.3 |
| Language | Python |
| Prerequisites | Python 3.10+, the openai package, and a MODEL_API_KEY (create one in the Model API dashboard). |
Model API exposes two request formats. Chat Completions is the messages-array endpoint this recipe uses: it is stateless, so you send the conversation on every request, and it covers most text generation, tool calling, structured output, and image input. The Responses API manages conversation state for you and adds built-in tools such as web search; a later recipe covers it.
The Model API is OpenAI-compatible: point the OpenAI SDK at the Model API base URL and set the model to muse-spark-1.3, and your existing code runs. It accepts most OpenAI Chat Completions parameters. Because muse-spark-1.3 is a reasoning model, a few parameters (such as stop, logit_bias, and logprobs without reasoning_effort="none") are not supported and return HTTP 400. The chat completion guide lists them all.
Setup
One cell wires up the client: it reads your key from the environment and falls back to Colab secrets when you run in a notebook.
pythonimport osfrom openai import OpenAI# Auth: in Colab, read the key from the secrets manager (the key icon in the# left sidebar); otherwise read the MODEL_API_KEY environment variable.try:from google.colab import userdataapi_key = userdata.get("MODEL_API_KEY")except Exception:api_key = os.environ.get("MODEL_API_KEY")if not api_key:raise RuntimeError("Set MODEL_API_KEY in your environment (or add it to Colab secrets).")# The OpenAI SDK does not auto-read MODEL_API_KEY, so pass it explicitly.client = OpenAI(base_url="https://api.meta.ai/v1", api_key=api_key)# Connectivity check: a one-line call that confirms the key and endpoint work.ping = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user", "content": "Reply with the single word: ready"}],)print(ping.choices[0].message.content)
Your first completion
A minimal request needs two things: a model and a messages array. Each message has a role and content. The model reads the array and returns a response that continues the conversation.
pythonresponse = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user", "content": "In two sentences, what is the Meta Model API?"},],)print(response.choices[0].message.content)print()print("finish_reason:", response.choices[0].finish_reason)print("usage:", response.usage)
The response object follows the OpenAI chat completion shape:
response.choices[0].message.content: the generated text.response.choices[0].finish_reason: why generation stopped.stopis a natural end,lengthmeans it hit the token cap, andtool_callsmeans the model wants to call a tool.response.usage: token counts. Track tokens to stay inside your team's rate limits.
Every parameter and response field is documented in the Chat completions API reference.
Steer behavior with a developer message
Add a developer message to set tone, format, or standing rules. It is the highest-precedence role you can set, and it overrides conflicting user instructions.
Muse Spark accepts a system role for OpenAI compatibility, but it applies that content at the developer level rather than overriding the model's own system prompt. Prefer developer. The chat completion guide explains the full instruction hierarchy.
pythonresponse = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "developer","content": "You are a terse assistant. Answer in exactly one sentence, with no preamble.",},{"role": "user", "content": "Why do reasoning models use chain-of-thought?"},],)print(response.choices[0].message.content)
Tune randomness with temperature
temperature controls variety, on a scale of 0–2. Lower values make output more focused and repeatable; higher values make it more varied. Muse Spark is tuned to run at the default of 1.0 and performs best there, so leave it unset for most workloads — the example below varies it only to show the effect. When you want more focused output, prefer clearer instructions over lowering temperature. Set temperature or top_p, not both.
pythonprompt = "Suggest a name for a Python logging library."for temp in (0.2, 1.5):response = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user", "content": prompt}],temperature=temp,)print(f"temperature={temp}: {response.choices[0].message.content}")
Statelessness is by design
Chat Completions is stateless: each request is answered using only the messages you send, with no memory of earlier calls. That is intentional: you control exactly what context the model sees. If you want the server to remember earlier turns for you, the Responses API keeps state with previous_response_id.
So a follow-up that refers back to an earlier turn fails unless you include that turn in the request. Ask an opinionated question, then ask the model to expand its previous answer in a fresh request:
python# Turn 1: an opinionated questionopinion = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user","content": "In one sentence, name the most underrated module in the Python standard library.",}],)print("Turn 1:", opinion.choices[0].message.content)# Turn 2, sent on its own: ask the model to expand the answer it just gavefollowup = client.chat.completions.create(model="muse-spark-1.3",messages=[{"role": "user","content": "Expand your previous answer into a short paragraph with an example.",}],)print("\nTurn 2 (no history):", followup.choices[0].message.content)
Turn 2 has no previous answer to expand: the model never saw turn 1, so it asks what you mean or picks something at random.
Send the conversation history so the second request can build on the first. Append the assistant's reply and the new user message, then send the full array:
pythonmessages = [{"role": "user","content": "In one sentence, name the most underrated module in the Python standard library.",},]opinion = client.chat.completions.create(model="muse-spark-1.3", messages=messages)messages.append({"role": "assistant", "content": opinion.choices[0].message.content})# Turn 2, with history: now "your previous answer" has a referentmessages.append({"role": "user","content": "Expand your previous answer into a short paragraph with an example.",})expanded = client.chat.completions.create(model="muse-spark-1.3", messages=messages)print("Turn 2 (with history):", expanded.choices[0].message.content)
Handle common error responses
Two failure classes are worth handling: 429 Too Many Requests (you exceeded the per-team rate limit) and 5xx or timeouts (transient server or network errors). Retry both with exponential backoff and jitter. Do not retry 400 errors: they mean the request is malformed, so fix it instead of resending.
pythonimport randomimport timefrom openai import APIStatusError, APITimeoutError, RateLimitErrordef complete_with_retry(messages, *, max_retries=5, **kwargs):"""Call chat completions, retrying transient 429/5xx/timeout errors."""delay = 0.5for attempt in range(max_retries):try:return client.chat.completions.create(model="muse-spark-1.3", messages=messages, **kwargs)except (RateLimitError, APITimeoutError) as exc:if attempt == max_retries - 1:raisesleep_for = delay * (2**attempt) + random.uniform(0, 0.5)print(f"Transient error ({type(exc).__name__}); retrying in {sleep_for:.1f}s")time.sleep(sleep_for)except APIStatusError as exc:# Retry server errors; re-raise client errors (such as 400) unchanged.if exc.status_code >= 500 and attempt < max_retries - 1:time.sleep(delay * (2**attempt) + random.uniform(0, 0.5))continueraiseresponse = complete_with_retry([{"role": "user", "content": "Give me one tip for handling API rate limits."}])print(response.choices[0].message.content)
Summary
| Pattern | How |
|---|---|
| First call | client.chat.completions.create(model="muse-spark-1.3", messages=[...]) |
| Read output | response.choices[0].message.content |
| Track tokens | response.usage (prompt_tokens, completion_tokens, total_tokens) |
| Steer behavior | a developer message (prefer it over system) |
| Control randomness | temperature (0–2, default 1.0 — leave unset; prefer clearer instructions over lowering it) or top_p, not both |
| Multi-turn | append assistant + user messages, then resend the full array |
| Resilience | retry 429 / 5xx with exponential backoff and jitter; never retry 400 |
Production checklist:
- Read
MODEL_API_KEYfrom the environment, never hard-code it. - Set a
developermessage for any non-trivial behavior. - Handle
429and 5xx with backoff; surface400to the caller. - Watch
usageand thex-ratelimit-remaining-*response headers to throttle before you hit a limit.
Next steps
- Stream tokens as they generate for responsive UIs: recipe 02 — Streaming responses.
- Let the model call your functions: recipe 03 — Tool calling.
- Force valid JSON out of the model: recipe 04 — Structured output.
- Control thinking effort and read
reasoning_content: recipe 06 — Reasoning tokens. - Go deeper in the chat completion guide and the API reference.