Short answer

Install the openai package, set LLM_BASE_URL, LLM_API_KEY and LLM_MODEL, and call client.chat.completions.create. The same code works with any OpenAI-compatible provider or a local server; only the three environment variables change. Handle timeouts and status errors explicitly, and read usage to see what you were billed.

Test status, 14 November 2026: Every Python sample on this page was run unchanged against the mock OpenAI-compatible server in this site's repository (scripts/mock-openai-server.mjs), including a case where the server returns 429 twice and the SDK retries. Environment: Python 3.12.4, openai 3.51.0 for Python, Windows 10. Not yet run against a live provider endpoint.

What you need

  • Python 3.10 or newer, which the current openai package requires. The examples were run on Python 3.12.
  • An API key from a provider, or a local OpenAI-compatible server such as llama.cpp’s llama-server, vLLM or Ollama. A local server does not need a real key, but the SDK requires a non-empty string.
  • The model id the provider expects. It is not always the Hugging Face repo name: compare the ids on our provider pages.

Install the SDK in a virtual environment:

bash code
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
python -m pip install openai

Keep the key out of the code

Put the three values in the environment, not in the script. That way the same file runs against any provider and never leaks a key into version control.

bash code
export LLM_BASE_URL="https://api.featherless.ai/v1"
export LLM_API_KEY="your-key-here"
export LLM_MODEL="huihui-ai/Huihui-Qwen3.8-27B-abliterated"

On Windows PowerShell use $env:LLM_API_KEY = "...". In production, use your platform’s secret store. The base URL and model above are one example; the API overview lists the documented base URL for each provider.

First request

python code
"""One chat completion against any OpenAI-compatible API.

Set LLM_BASE_URL, LLM_API_KEY and LLM_MODEL in the environment first.
"""
import os
import sys

from openai import OpenAI, APIConnectionError, APIStatusError, APITimeoutError

client = OpenAI(
    base_url=os.environ["LLM_BASE_URL"],
    api_key=os.environ["LLM_API_KEY"],
    timeout=60.0,      # seconds for the whole request
    max_retries=2,     # the SDK retries 408, 409, 429 and 5xx with backoff
)

try:
    reply = client.chat.completions.create(
        model=os.environ["LLM_MODEL"],
        messages=[
            {"role": "system", "content": "You are a concise assistant for developers."},
            {"role": "user", "content": "Explain what a race condition is in two sentences."},
        ],
        max_tokens=300,
    )
except APITimeoutError:
    sys.exit("The request timed out. Try a smaller max_tokens or a longer timeout.")
except APIConnectionError as e:
    sys.exit(f"Could not reach {os.environ['LLM_BASE_URL']}: {e}")
except APIStatusError as e:
    # 401 bad key, 404 wrong base URL or model, 413 prompt too long, 429 rate limit
    sys.exit(f"The API returned {e.status_code}: {e.message}")

choice = reply.choices[0]
print(choice.message.content)
print(f"finish_reason={choice.finish_reason}")
if reply.usage:
    print(f"tokens: {reply.usage.prompt_tokens} in, {reply.usage.completion_tokens} out")

The request has the usual chat format: a list of messages with roles system, user and assistant. The SDK sends Authorization: Bearer <key> and posts to <base URL>/chat/completions.

Three details are worth knowing:

  • finish_reason tells you why the reply ended: stop for a natural end, length when it hit max_tokens or the provider’s output cap. A length reply is cut off; do not treat it as complete.
  • usage reports the tokens billed. Use real numbers from here in the token cost calculator instead of guessing.
  • Retries. max_retries=2 lets the SDK retry rate limits (429) and server errors with backoff. In our test the mock server returned 429 twice and the third attempt succeeded without any code of ours.

Streaming

For anything longer than a sentence or two, stream the reply so the user sees text as it is generated. Thinking models in particular can spend many seconds reasoning before the first visible token.

python code
"""Stream a reply token by token, then print the usage if the server sends it."""
import os

from openai import OpenAI

client = OpenAI(base_url=os.environ["LLM_BASE_URL"], api_key=os.environ["LLM_API_KEY"], timeout=60.0)

stream = client.chat.completions.create(
    model=os.environ["LLM_MODEL"],
    messages=[{"role": "user", "content": "Write a haiku about version control."}],
    max_tokens=200,
    stream=True,
    stream_options={"include_usage": True},  # some servers ignore this; usage may be absent
)

parts = []
usage = None
try:
    for chunk in stream:
        if chunk.usage:
            usage = chunk.usage
        if not chunk.choices:
            continue
        delta = chunk.choices[0].delta
        if delta.content:
            parts.append(delta.content)
            print(delta.content, end="", flush=True)
finally:
    stream.close()  # releases the connection even if you stop early

print()
if usage:
    print(f"tokens: {usage.prompt_tokens} in, {usage.completion_tokens} out")
print(f"characters received: {len(''.join(parts))}")

stream_options={"include_usage": True} asks for a final chunk with token counts. Not every server honours it: some always send usage, some ignore the option, and the chunk carrying usage has an empty choices list, which is why the loop checks chunk.choices first. The streaming guide explains the event format and how to cancel a stream.

Errors you will meet

Status Usual cause What to do
401 Missing or wrong key Check the environment variable is set in the process that runs the code
404 Wrong base URL path or unknown model id Compare the base URL with the provider’s docs; list models
413 or 400 Prompt plus output exceed the context limit Shorten the input or reserve less output; see the context window planner
429 Rate limit or quota Let the SDK retry; if it persists, slow down or check your plan
503 Model not available on the provider right now Retry later or choose another model; do not assume another model will answer the same way

Some providers return their own error types inside the JSON body. The SDK’s APIStatusError exposes status_code and message, which is enough to log and decide.

Switching providers

Only the environment changes. These are documented base URLs; the model ids are examples from our data:

Where the model runs LLM_BASE_URL LLM_MODEL example
Featherless https://api.featherless.ai/v1 huihui-ai/Llama-3.3-70B-Instruct-abliterated
Venice https://api.venice.ai/api/v1 venice-uncensored-1-2
MolochAI https://api.molochai.dev/api/v1 llama-3.3-70b-instruct-abliterated
llama.cpp llama-server on your machine http://localhost:9931/v1 the name set with --alias

The parameters each provider supports differ even when the endpoint is the same. Before relying on response_format, seed or stop, read the OpenAI compatibility guide, which lists what each provider documents as supported or ignored.

Troubleshooting

  • The reply is empty and finish_reason is length. A thinking model used the whole max_tokens budget on reasoning. Raise max_tokens, or lower the reasoning effort if the model supports it.
  • The answer contains <think> text. Some servers return reasoning inside the content, others in a separate field. Check the model card and the provider’s docs for how reasoning is returned.
  • Requests hang. Set timeout on the client, as in the first example, and stream long replies.

Questions

Do I need a special SDK for an uncensored model?

No. Hosted providers of refusal-reduced models and local servers such as llama.cpp and vLLM expose the OpenAI chat-completions format, so the official openai package works with a different base URL.

Why does my request return 404?

Usually the base URL is missing a path segment (some providers use /api/v1, others /v1) or the model id is not one the provider lists. Call client.models.list() to see the ids it accepts.

Can I set max_tokens above the provider's limit?

You can send it, but providers cap output per reply, and some replace your value with their cap. Check the provider's documented maximum output.

Sources

  1. openai-python, GitHub (OpenAI)
  2. Chat Completions API reference, OpenAI
  3. llama.cpp HTTP server, GitHub (ggml-org)
  4. OpenAI-Compatible Server, vLLM documentation

Found an error? Report it. We log material corrections publicly.