Use the OpenAI SDK with DeepSeek, Qwen and GLM: one base_url
5 min readWritten by genmux API Team
If your codebase already calls openai.chat.completions.create(...), switching to DeepSeek, Qwen3 or Doubao through genmux is a two-line change: point base_url at https://api.genmux.tech/v1 and swap the API key. Everything downstream — request shape, response shape, streaming — stays the same. This post is the field-by-field reference for what "OpenAI-compatible" actually covers here. (GLM and Kimi are temporarily off the roster while their upstream is being fixed — and more models come online over time.)
The two-line migration
from openai import OpenAI
client = OpenAI(
base_url="https://api.genmux.tech/v1", # was https://api.openai.com/v1
api_key="nb_live_...", # your genmux key
)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.genmux.tech/v1",
apiKey: process.env.GENMUX_API_KEY,
});Every call after this looks exactly like it did against OpenAI's own API — same method names, same response objects — with model pointing at one of genmux's ids instead. Since v0.4.0 that includes function calling and JSON mode, which is what makes the endpoint a drop-in backend for coding agents.
What's supported
POST /v1/chat/completions accepts the standard fields:
| Field | Supported | Notes |
|---|---|---|
model | yes | Any id in the pricing table, e.g. deepseek-v4-pro, qwen3-max, doubao-seed-2-pro, and more as they come online |
messages | yes | role: system | user | assistant, content |
max_tokens | yes | Caps the reservation; a value above the model's max_output_tokens is clamped, not rejected |
temperature, top_p, stop | yes | Standard sampling controls |
stream | yes | Server-sent events; final chunk carries usage; tool calls arrive as delta.tool_calls |
tools, tool_choice | yes, 7 models | Function calling, emulated by genmux; assistant tool_calls and role: tool messages round-trip as with OpenAI. Other models: 400 tools_unsupported |
response_format | yes | json_object and json_schema ({name, schema}); non-streamed replies are validated, one automatic retry, then 502 json_invalid. Not combinable with tools |
reasoning_effort | yes | none/minimal turn thinking off, low…xhigh turn it on, on thinking-capable models |
enable_thinking | genmux extension | Thinking-capable models only (see below) |
web_search | genmux extension | Doubao Seed 2.0 Pro only |
Function calling and JSON mode
tools works on the seven models flagged params_schema.tools: true in GET /v1/models: deepseek-v4-pro, deepseek-v4-flash, deepseek-v3, qwen3-7-max, qwen3-max, doubao-seed-2-pro and doubao-seed-2-code. The upstream gateway drops the tools field, so genmux renders the definitions into the system prompt and parses the reply back into standard tool_calls — the definitions count as prompt tokens (about 40 per tool plus schema), and tool_choice: required is an instruction the model can ignore. Sending tools to qwen3-6-flash, doubao-seed-2-lite or doubao-seed-2-mini returns 400 tools_unsupported. The engineering and the measured pass rates are in the tool-calling post.
response_format takes {"type":"json_object"} and {"type":"json_schema","json_schema":{"name":…,"schema":{…}}}. Non-streamed replies are validated (JSON Schema 2020-12); a failure triggers one automatic retry with the validator's error fed back, and a second failure ends in 502 json_invalid with both attempts billed. Streams pass through unvalidated, and JSON mode cannot be combined with tools in one request.
What isn't supported
Three things return 400 unsupported_parameter rather than being silently ignored:
n > 1— one completion per request only.- legacy
functions— usetools; the deprecatedfunctions/function_callpair is rejected. logprobs— not returned by any upstream.
Accepted and ignored, so old client code keeps working: stream_options, parallel_tool_calls, seed, user, metadata, store, logit_bias and the per-message name.
Reading usage and cost
The response is a standard chat.completion (or a stream of chat.completion.chunk), extended with billing fields on usage:
{
"id": "gnx_9f2c...",
"object": "chat.completion",
"model": "qwen3-max",
"choices": [{ "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" }],
"usage": {
"prompt_tokens": 128,
"completion_tokens": 342,
"total_tokens": 470,
"cost": 0.276,
"cost_source": "vendor"
}
}id is the genmux job id — an OpenAI SDK will happily ignore it if you don't need it, but it's there if you want to look the call up later via GET /v1/generations/{id}, or list every chat call on the account with GET /v1/generations?capability=text.chat. cost is in credits; cost_source is vendor when the upstream reported exact usage, or estimated when genmux computed it from token counts (always the case for streamed calls).
Errors worth handling explicitly
Errors on the chat route come in the OpenAI envelope — {"error":{"message","type","code","param","request_id"}} with type one of invalid_request_error, authentication_error, permission_error, insufficient_quota, rate_limit_error, not_found_error, server_error — so the OpenAI SDK's exception classes map onto them unchanged. (Other genmux routes keep RFC 7807 problem+json.)
| Status | Code | Meaning |
|---|---|---|
| 400 | unsupported_parameter | One of the rejected fields above (n>1, functions, logprobs) was sent |
| 400 | tools_unsupported | tools sent to one of the three models without the flag |
| 400 | context_length_exceeded | Prompt + max_tokens exceeds the model's context window |
| 402 | insufficient_credits | Balance can't cover the reservation; nothing was generated or billed |
| 502 | json_invalid | JSON mode reply failed schema validation twice; both attempts billed |
Thinking, if the model supports it
enable_thinking works on DeepSeek V4 (Pro and Flash), Qwen3.7-Max, Qwen3-Max, and Doubao Seed 2.0 Pro/Code/Lite, with more models gaining it as they come online — sending it to a model that doesn't support reasoning is simply ignored. Reasoning tokens are billed as completion tokens; see how thinking is billed before flipping it on for high-traffic endpoints.
Streaming stays the same shape
stream: true returns the same SSE format your OpenAI integration already parses — data: {...}\n\n lines, data: [DONE] to close. The one thing to add: the last chunk before [DONE] has an empty choices array and a populated usage object, which is where your cost accounting should read from. Full parsing code for Python and Node is in the streaming guide.
Getting started
Change base_url, drop in a genmux key, and the rest of your OpenAI integration keeps working — tools and JSON mode included, as long as it isn't relying on n>1, legacy functions or logprobs. Agent setups (OpenCode, aider, Cline, Roo Code, Continue) are on the coding agents page. Full endpoint reference and the current model catalogue are in the docs; prices for every model are on the pricing page.