Skip to main content

Python SDK for Anthropic, OpenAI, MiniMax, Gemini, and Ollama AI providers

Project description

motosan-ai (Python SDK)

Multi-provider Python SDK for Anthropic, OpenAI, MiniMax, Ollama, Gemini, and Claude Code CLI. All HTTP providers use httpx directly — no official provider SDKs required. Also includes a ClaudeCodeClient backend that shells out to local claude CLI.

Installation

pip install motosan-ai
pip install "motosan-ai[anthropic]"
pip install "motosan-ai[openai]"
pip install "motosan-ai[minimax]"
pip install "motosan-ai[ollama]"
pip install "motosan-ai[gemini]"
pip install "motosan-ai[full]"

Quick Start

import asyncio

from motosan_ai import Client


async def main() -> None:
    client = Client.anthropic(api_key="sk-ant-...", model="claude-sonnet-4-6")
    response = await client.chat([
        {"role": "user", "content": "Hello"},
    ])
    print(response.content)


asyncio.run(main())

Tool Use (Multi-turn)

import asyncio

from motosan_ai import Client, Message, Tool


def get_weather(city: str) -> str:
    return f"Sunny in {city}"


async def main() -> None:
    client = Client.anthropic(api_key="sk-ant-...")

    tools = [
        Tool(
            name="get_weather",
            description="Get current weather",
            input_schema={
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        )
    ]

    messages = [Message.user("What's the weather in Tokyo?")]
    response = await client.chat(messages, tools=tools)

    if response.tool_calls:
        tc = response.tool_calls[0]
        result = get_weather(tc.input["city"])

        messages += [
            Message.assistant_with_tool_calls("", response.tool_calls),
            Message.tool_result(tc.id, result),
        ]
        final = await client.chat(messages, tools=tools)
        print(final.content)


asyncio.run(main())

Streaming

import asyncio

from motosan_ai import Client, Message


async def main() -> None:
    client = Client.openai(api_key="sk-...", model="gpt-4o")

    async for event in client.stream([Message.user("Write a haiku about rain")]):
        if event.content:
            print(event.content, end="")
        if event.done:
            break


asyncio.run(main())

Retry

All API calls automatically retry on transient errors (429 rate limit, 5xx server errors, network timeouts). Default: 3 retries with exponential backoff (100ms, 200ms, 400ms).

# Default: 3 retries
client = Client.anthropic(api_key="...")

# Disable retry
client = Client.anthropic(api_key="...", max_retries=0)

# Custom retry count
client = Client.anthropic(api_key="...", max_retries=5)

Respects Retry-After header when present.

Sync Wrapper

from motosan_ai import Client, Message

client = Client.minimax(api_key="...")
response = client.chat_sync([Message.user("Hello from sync")])
print(response.content)

Providers

Anthropic

from motosan_ai import Client

client = Client.anthropic(api_key="sk-ant-...", model="claude-sonnet-4-6")

OpenAI

from motosan_ai import Client

client = Client.openai(api_key="sk-...", model="gpt-4o")

MiniMax

from motosan_ai import Client

client = Client.minimax(api_key="...", model="MiniMax-M1")

Ollama

from motosan_ai import Client

# OpenAI-compatible mode (default)
client = Client.ollama(model="llama3.2")

# Native Ollama API mode (supports think/keep_alive/num_ctx)
client = Client.ollama(model="llama3.2", native=True, think=True)

Claude Code CLI Backend

from motosan_ai import ChatRequest, ClaudeCodeClient, Message

client = (
    ClaudeCodeClient()
    .model("sonnet")
    .system_prompt("Be concise.")          # --system-prompt
    .permission_mode("plan")               # --permission-mode plan
    .effort("low")                         # --effort low
    .allow_tool("Read")                    # --allowed-tools Read
    .max_budget_usd(2.5)                   # --max-budget-usd 2.5
)

response = await client.chat(
    ChatRequest(messages=[Message.user("Hello from claude CLI")])
)
print(response.content)

async for event in client.stream(
    ChatRequest(messages=[Message.user("Stream a short poem")])
):
    if event.event_type == "usage":
        print(f"\nusage={event.usage}")
    elif event.content:
        print(event.content, end="")
    if event.done:
        break

Notes:

  • Uses CLAUDE_CODE_PATH env var or claude in PATH.
  • tool_calls is always empty (tools run inside CLI).
  • agent_mode(True) enables --dangerously-skip-permissions + JSON output parsing.
  • Python v0.9.0 adds full Rust-compatible Claude Code flag coverage: bare, system_prompt, permission_mode, effort, fallback_model, add_dir(s), allow_tool / allowed_tools, disallow_tool / disallowed_tools, mcp_config(s), strict_mcp_config, settings, setting_source(s), session_id, resume, continue_latest, fork_session, plugin_dir(s), agent, no_session_persistence, and max_budget_usd.
  • system_prompt(...) maps to --system-prompt; system messages / ChatRequest.system are appended with --append-system-prompt.
  • allowed_tools, disallowed_tools, and mcp_configs are variadic CLI arguments, matching Rust (--allowed-tools Read Bash, not comma-joined).
  • Streaming emits StreamEvent(event_type="usage") before the terminal done event when Claude Code includes token usage in the NDJSON result event.

Anthropic Auth Matrix

  • sk-ant-api* or regular Anthropic API key → x-api-key header
  • sk-ant-oat01* OAuth token → OAuth mode:
    • Authorization: Bearer <token> header (via httpx directly)
    • anthropic-beta: claude-code-20250219,oauth-2025-04-20,... headers
    • user-agent: claude-code/<version> + x-app: cli identity headers
    • System prompt sent as array of blocks (prefix + user system)
    • Claude Code system prompt prefix auto-injected
    • chat() auto-redirects to stream() and collects result (including tool_calls)

The SDK auto-detects token type by prefix — pass either into Client.anthropic(api_key=...).

from motosan_ai import Client

# Standard API key
client = Client.anthropic(api_key="sk-ant-api03-...")

# OAuth token (auto-detected, same interface)
client = Client.anthropic(api_key="sk-ant-oat01-...")

HTTP Client

All providers use httpx directly — no official provider SDKs (anthropic, openai) required. This keeps the dependency tree minimal and gives full control over auth, headers, and SSE parsing.

Requirements

  • Python 3.11+
  • One provider API key:
    • ANTHROPIC_API_KEY (standard API key or OAuth token)
    • OPENAI_API_KEY
    • MINIMAX_API_KEY
    • Ollama: no key needed (local)

Testing

# Unit tests (mock, no API needed)
uv run pytest sdks/python/tests/ -q --ignore=sdks/python/tests/integration/

# Live integration tests (requires ANTHROPIC_API_KEY)
ANTHROPIC_API_KEY=... uv run pytest sdks/python/tests/integration/test_anthropic_live.py -v

Publishing

Automated via publish-python.yml on python-v* tag push → PyPI.

# Tag and push to trigger publish
git tag -a python-vX.Y.Z -m "python-vX.Y.Z — summary"
git push origin python-vX.Y.Z

# Manual (emergency)
uv build --out-dir dist && uv publish dist/*

Rust and Python SDKs are versioned independently.

Development

uv sync --extra full --extra dev
uv run ruff check motosan_ai/
uv run pytest -q

For AI Agents

If you're an AI coding assistant, fetch llms.txt for a quick-start guide with API examples, tool use patterns, and streaming setup.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

motosan_ai-0.9.0.tar.gz (55.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

motosan_ai-0.9.0-py3-none-any.whl (32.4 kB view details)

Uploaded Python 3

File details

Details for the file motosan_ai-0.9.0.tar.gz.

File metadata

  • Download URL: motosan_ai-0.9.0.tar.gz
  • Upload date:
  • Size: 55.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for motosan_ai-0.9.0.tar.gz
Algorithm Hash digest
SHA256 f137abe0194d61048237dbb5753788f2cf9ca89bb042e8efeaa65be5fab7216b
MD5 0b064ed12feec409b78b8ed34d3a2741
BLAKE2b-256 1d963aa8daebe90034fd6a6838fcd8be8d45a7bd2e6694ffebb8a5233bc32c66

See more details on using hashes here.

File details

Details for the file motosan_ai-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: motosan_ai-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 32.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for motosan_ai-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 63f3d0e342d88a2a2d72fc8a48a12b84022526b4dbffef985d58bbbf757ac8b3
MD5 2ebfdd1f4cc95e4278298ca452b46be8
BLAKE2b-256 9b764381e4ee0dbec594b635b4ae50560f517adc2686665f00f17af4b6df8e93

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page