Skip to main content

multichat-sdk (Python)

Change one import. Your code keeps calling OpenAI or Anthropic directly, with your own key, and multiChat gives it a durable conversation store, failover to a backup model, and the planner's savings shown before anything is changed.

from multichat import OpenAI          # was: from openai import OpenAI

client = OpenAI()                     # OPENAI_API_KEY as always; MULTICHAT_API_KEY for the store
r = client.chat.completions.create(model="gpt-5.1", messages=[{"role": "user", "content": "Hello"}])

multichat.OpenAI, AsyncOpenAI, Anthropic and AsyncAnthropic are subclasses of the official clients: every argument, method and return type is theirs. Around each chat.completions.create (OpenAI) or messages.create / messages.stream (Anthropic) call the SDK:

  1. finds the call's conversation (one store session per conversation) by matching the message list against what it recorded before, or by the conversation= handle you pass;
  2. sends only the new messages to the store as SYNC lines, in the background (the provider call is never delayed or broken by the store; lines wait and go with the next turn if it is down);
  3. records the reply and its usage (input, output, cache_read, cache_write, thinking, time, and who answered);
  4. asks the planner what it would change (optimise="preview", the default) and shows it in client.multichat.last.changes and on the multichat logger, without changing what you send;
  5. with Anthropic prompt caching and keepalive= on, schedules the cache keep-alive on the queue and cancels it when your next turn starts.

Nothing is routed through multiChat: the request goes from your process to the provider.

Install

pip install "multichat-sdk[openai]"      # or [anthropic], or [all]

Python 3.9+. The import name is multichat. (An unrelated PyPI package called multichat installs the same import name; do not install both.)

Sign in

  • MULTICHAT_API_KEY: an API key from the portal (for servers and CI), or
  • multichat login (or python -m multichat login): signs in through the portal in your browser and saves a token to ~/.multichat/credentials.json (mode 0600).

multichat_api_key= on the client wins over both. multiChat never breaks your app: without a credential, or when the multiChat account is invalid or out of credit (the store answers 401, 402 or 403), the client calls the provider directly and records nothing, with one warning on the multichat logger. record=False does the same on purpose (failover still works). Only the explicit multiChat calls (advice, spend, files, forks) raise NotLoggedInError. Service addresses: MULTICHAT_STORE_URL, MULTICHAT_QUEUE_URL, MULTICHAT_PLANNER_URL.

Conversations

Calls are grouped by their message lists: a call whose messages start with what a conversation recorded (or, for a trimmed history, contain its last reply) continues it; anything else starts a new session. To be explicit:

conv = client.multichat.conversation()            # or conversation("<session ID>") to continue one
client.chat.completions.create(model=..., messages=msgs, conversation=conv)
print(conv.session_id, conv.turn)

client.multichat.last is the last turn's TurnInfo: session_id, turn, model, answered_by, usage, ms, sent (the body that went to the provider), changes, x, y. client.multichat.flush() waits until the store has everything; close() and with do it too.

Optimise

  • optimise="preview" (default): the planner's proposal (model on the flip ladder, compaction, cache markers, with the X vs Y cost) is fetched after each turn and shown; nothing changes.
  • optimise=True: the turn is stored first and the assembled request from the store is sent instead of yours (TurnInfo.sent and changes show exactly what and why). If the store is unreachable, your own request goes.
  • optimise=False: no planner call.

Set it on the client or per call (create(..., optimise=True)).

Advice and guardrails

from multichat import OpenAI, Guardrails, GuardrailError
client = OpenAI(guardrails=Guardrails(max_session_usd=2.0, max_turn_usd=0.25, max_context_tokens="session"))
advice = client.multichat.advice()        # {spend, findings: [{title, cost_usd, saving_usd, fixes}]}
for fix in advice["findings"][0]["fixes"]:
    client.multichat.apply_fix(fix)       # an SDK option, or a fork with better settings

A limit passed raises GuardrailError before the provider is called (on_limit="warn" sends anyway). stable_tools=True sorts tools by name so an order change does not break the prompt cache. Details: docs/v2/advice.md.

Failover

from multichat import OpenAI, Anthropic, Backup

client = OpenAI(failover=Backup(client=Anthropic(), model="claude-sonnet-4-5", after_ms=3000))
r = client.chat.completions.create(model="gpt-5.1", messages=msgs)   # always a ChatCompletion
  • The primary starts at once. If it has sent no token after after_ms, the backup starts too; the first to answer is used. A rate limit, overload (429, 529, 503...), timeout or connection failure starts the backup at once; other errors (a bad request, a bad key) are raised as they are.
  • The request is converted locally (OpenAI and Anthropic messages, images, documents, tool calls and tool results; convert="service" uses the planner's /v2/convert), and the answer comes back in the primary's shape: a ChatCompletion for OpenAI, a Message for Anthropic, or their stream events when you streamed (the backup's answer then arrives whole as a short stream).
  • Per call: create(..., failover=Backup(...)), or failover=None to turn it off.
  • Backup(params={"max_tokens": 1024}) adds fields for the backup only. Sampling settings (temperature, top_p) are not carried to Anthropic, whose current models do not take them.
  • Queue mode: Backup(..., mode="queue", key_mode="held") asks the multiChat queue to send the backup at after_ms with your backup key (held encrypted, deleted once fired), or key_mode="webhook", webhook="https://..." to have your endpoint send it. The primary's first token cancels it. If the queue cannot be reached the in-process timer is used.

Keep-alive (Anthropic)

client = Anthropic(keepalive=KeepAlive())           # or keepalive=True

When a request carries cache_control, the queue sends the exact request again (max_tokens=1) at send time + TTL - 30 s (TTL 5 minutes, or 1 hour), and again a TTL later while the session's enhanced cache extensions last (the queue keeps the count, so a chain never goes past what the session allows), so the cache is warm for your next turn; that turn cancels the chain. A client pointed at another deployment (base_url) has its keep-alives and queue-mode backups sent there. The queue needs your key: it is held encrypted until the task fires (key_mode="held"), or use key_mode="webhook". Off by default.

Keeping the official client

import multichat, openai

client = openai.OpenAI()
messages = multichat.messages(client=client)          # a list the official client takes as is
messages.add_prompt("Describe this", images=["https://example.com/cat.jpg"])
r = client.chat.completions.create(model="gpt-5.1", messages=messages)
messages.add_response(r)                              # appended and recorded
  • Session(client, model=..., optimise=..., session_id=...): one conversation in the store.
  • Messages(session): the session's messages in the client's format (Anthropic's system kept in messages.system); add_system, add_prompt(text, images=, documents=), add_response(r), add_tool_result(call_id, output), render("anthropic") for the other provider.
  • Model(name, session): the name to send; with optimise=True the planner's model for the turn.
  • turn(session, failover=...):
async with multichat.turn(session, failover=multichat.Backup(client=claude, model="claude-sonnet-4-5")) as t:
    t.messages.add_prompt("And in French?")
    r = await t.call(client.chat.completions.create)      # calls with t.model and t.messages, records the reply

t.add_response(r) records a reply you got yourself. A turn that closes without a recorded reply warns (RuntimeWarning).

Workspace (run_code)

Give the model a private workspace per conversation: Python or bash, a /work folder kept for 24 hours, the account's files in (files_in) and every file the run makes saved to the account's files. Answer its run_code calls with Workspace.handle:

from multichat import OpenAI, Workspace, workspace

client = OpenAI()
ws = Workspace()                                 # MULTICHAT_WORKSPACE_URL overrides the address
reply = client.chat.completions.create(model="gpt-5.1", messages=msgs, tools=[workspace.openai_tool()])
for call in reply.choices[0].message.tool_calls or []:
    if call.function.name == workspace.TOOL_NAME:
        result = ws.handle(session_id, call.function.arguments)   # the tool result text

workspace.anthropic_tool() for Anthropic. The call and its result are recorded with the next turn, like any tool call. Details: docs/v2/workspace.md.

Files

Your files live in your account's regional bucket; conversations hold a reference (mcfile:<id>), and the store gives providers a short-lived signed link, or the bytes inline for a provider that does not fetch links.

f = client.multichat.files.upload("chart.png", session=client.multichat.last.session_id)
url = client.multichat.files.link(f["id"], ttl=600)["url"]      # at most 7 days
client.multichat.files.save_to_project(f["id"], "launch")        # kept when the chat is deleted

Inline images, audio and documents in your requests still go to the provider exactly as written; the store records the uploaded file's reference instead of the bytes (record_files=False keeps the bytes). A file tied to a conversation is deleted with it unless saved to a project.

Conversion

multichat.convert_request(req, "openai.chat", "anthropic.messages", model) and multichat.convert_response(resp, from_format, to_format) are the converters failover uses.

Tests

uv run pytest

Unit tests only: the services are an in-memory transport and the providers are fake servers behind the official clients' own mock transport. Nothing reaches a network or a provider.

Licence

Apache License 2.0: see LICENSE, with the attribution notices in NOTICE. Copyright 2026 Digital Deep Ltd.

Metadata

Release files for multichat-sdk 0.2.0

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

Source distribution (sdist)

Source distribution for multichat-sdk 0.2.0
File Size Uploaded Provenance Actions
multichat_sdk-0.2.0.tar.gz 73.5 kB With provenance Details for multichat_sdk-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for multichat-sdk 0.2.0
File Interpreter ABI Platform Actions
multichat_sdk-0.2.0-py3-none-any.whl Python 3 none any Details for multichat_sdk-0.2.0-py3-none-any.whl

Total release size: 138.7 kB

Release files / multichat_sdk-0.2.0.tar.gz

Download URL multichat_sdk-0.2.0.tar.gz
Size 73.5 kB
Tags Source
SHA-256 checksum
How to use checksums
7788477b9a2a728352161f5b93c27d227cfa0d230bee8900e3dfd61ab84cb6d4
BLAKE2b-256 checksum
How to use checksums
c130f510815508ad5340f997d77d4b7ef473067ab3de61b83cb46d64dcc0e02f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

Release files / multichat_sdk-0.2.0-py3-none-any.whl

Download URL multichat_sdk-0.2.0-py3-none-any.whl
Size 65.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cf3a53b534a3984176cabeff5150c48680135e294054aad1284358573ea06e71
BLAKE2b-256 checksum
How to use checksums
945327476ca6c9501c1e676f07dca0169cb99b369da8822d0fe61cc619e3fbb8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

Security for multichat-sdk 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for multichat_sdk-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

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