Last reviewed: October 16, 2026
Yes, you can use the OpenAI SDK with DeepSeek for DeepSeek’s OpenAI-compatible API endpoints. The important part is understanding what changes: you install the OpenAI SDK as the client library, but you use a DeepSeek API key, DeepSeek’s base URL, and a current DeepSeek model name.
DeepSeek’s documentation uses the openai package, imports OpenAI, sets base_url="https://api.deepseek.com", and calls client.chat.completions.create() with model="deepseek-flash". See the official DeepSeek Python examples.
Using the OpenAI SDK this way does not mean you are using OpenAI models, OpenAI billing, or an OpenAI API key. The SDK is only the client. Because the base URL points to DeepSeek, the request goes to DeepSeek and is billed by DeepSeek under DeepSeek’s API terms.
This guide covers Python setup, a first request, streaming, async Python, JavaScript and TypeScript, JSON Output, Tool Calls, and Thinking Mode. For the Chat Completions examples, Python uses extra_body for the DeepSeek-specific thinking field; JavaScript places that field directly in the request object. A compatible SDK response still needs application checks before its text or tool requests are accepted.
Quick answer: what changes from OpenAI to DeepSeek?
If you already know the OpenAI SDK, switching a simple chat request to DeepSeek mainly requires changing three things: the API key, the base URL, and the model name.
| Setting | OpenAI API | DeepSeek with OpenAI SDK |
|---|---|---|
| SDK package | openai | openai |
| API key | OpenAI API key | DeepSeek API key |
| Environment variable | Often OPENAI_API_KEY | Use DEEPSEEK_API_KEY to avoid confusion |
| Base URL | OpenAI default endpoint | https://api.deepseek.com |
| Model name | OpenAI model ID | Current DeepSeek model ID, such as deepseek-flash |
| Billing provider | OpenAI | DeepSeek |
The most common mistakes are using an OpenAI API key, forgetting the base URL, or copying an OpenAI model name into a DeepSeek request.
Prerequisites
Before you start, you need:
- A DeepSeek API key.
- A Python version supported by the installed OpenAI SDK, or a supported Node.js environment for the JavaScript examples.
- The
openaipackage installed in the environment that will run your code. - A current DeepSeek model name, such as
deepseek-flash. - Access to your terminal so you can set environment variables.
API usage is billed separately from the DeepSeek chat app. Review your DeepSeek API balance and billing and the current provider pricing before running examples. The weather handler later in this guide is a local mock, but the model requests around it are billable.
Do not paste your API key directly into your Python file. Store it in an environment variable or a secrets manager.
Install the OpenAI SDK
Install the package into the same Python environment that will run the examples:
python -m pip install openaiOn a system where the Python interpreter is named python3, use:
python3 -m pip install openaiOpenAI’s own SDK documentation also lists pip install openai for Python and npm install openai for JavaScript. The SDK package is maintained by OpenAI, but in this tutorial it is configured as a client for DeepSeek’s OpenAI-compatible endpoint. See the OpenAI SDK documentation.
Set your DeepSeek API key
Use DEEPSEEK_API_KEY instead of OPENAI_API_KEY so you do not accidentally send DeepSeek requests with the wrong key.
Need to create or rotate that credential? Follow our DeepSeek API key guide.
On macOS or Linux:
export DEEPSEEK_API_KEY="your-deepseek-api-key"On Windows PowerShell:
$env:DEEPSEEK_API_KEY="your-deepseek-api-key"In production, store keys in your hosting platform’s secret manager rather than in shell history, source files, notebooks, screenshots, or issue reports.
Make your first DeepSeek request with the OpenAI SDK
This Python starter configures the provider and checks for a completed, nonempty text answer. The small checked_text_message function is an application helper defined here, not an SDK method. The Thinking Mode and streaming snippets below reuse this client; the Thinking Mode snippets also reuse the helper.
import os
from openai import OpenAI
def checked_text_message(response):
choices = getattr(response, "choices", None)
if not isinstance(choices, list) or len(choices) != 1:
raise RuntimeError("Expected exactly one response choice.")
choice = choices[0]
message = getattr(choice, "message", None)
if (getattr(choice, "finish_reason", None) != "stop"
or message is None
or getattr(message, "role", None) != "assistant"
or getattr(message, "tool_calls", None)):
raise RuntimeError("Expected a completed text response; workflow stopped.")
content = getattr(message, "content", None)
if not isinstance(content, str) or not content.strip():
raise RuntimeError("No usable final text was returned.")
return message
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
max_retries=0,
)
response = client.chat.completions.create(
model="deepseek-flash",
messages=[
{"role": "system", "content": "You are a concise technical assistant."},
{"role": "user", "content": "Explain Python decorators in one paragraph."},
],
extra_body={"thinking": {"type": "disabled"}},
)
print(checked_text_message(response).content)This starter explicitly disables Thinking Mode. DeepSeek documents thinking as enabled by default; disabling it does not guarantee deterministic output. The extra_body syntax belongs to the Python SDK. A normal stop is not proof that an answer is factually correct; the helper only checks the response state and text shape.
The examples set max_retries=0 so SDK retries do not silently resubmit demonstration requests. Add a deliberate retry policy and an application-appropriate timeout before deployment. Close a long-lived client with client.close() when the application is finished with it, not before running snippets that reuse it. See the SDK retry and timeout options.
How the base_url setting works
The base URL tells the SDK where to send requests. Omitting it leaves the SDK to its default or other configured endpoint; it does not automatically select DeepSeek. Set the DeepSeek base URL explicitly, and use the client option for your language.
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
max_retries=0,
)DeepSeek’s Models & Pricing page lists https://api.deepseek.com as the OpenAI-format base URL. It also lists https://api.deepseek.com/anthropic as the Anthropic-format base URL. Do not use the Anthropic-format URL with these OpenAI SDK examples. See DeepSeek Models & Pricing.
Which DeepSeek model should you use?
DeepSeek’s current Models & Pricing page lists these two API model IDs:
| Model ID | Documented model version | Good starting point for |
|---|---|---|
deepseek-flash | DeepSeek-V4.1-Flash | Most new examples, general chat, long-context workflows, vision, and lower-cost API usage. |
deepseek-v4-pro | DeepSeek-V4-Pro-0813 | Workloads evaluated for the separate Pro model; compare its behavior and cost with Flash before choosing. |
DeepSeek’s pricing page says legacy names such as deepseek-v4-flash and deepseek-v4-flash-vision-exp are still accepted, but the corresponding models have been retired and requests are served by DeepSeek-V4.1-Flash. For new code, use deepseek-flash.
Enable or disable Thinking Mode
DeepSeek documents Thinking Mode as enabled by default with high effort. In Python, pass the provider-specific thinking field through extra_body. In JavaScript, put thinking directly beside model and messages. These are two SDK syntaxes for the same top-level API field. See the DeepSeek Thinking Mode guide and the OpenAI Python SDK’s extra-parameter options.
The Python snippets in this section reuse the client created in the first-request example.
Disable Thinking Mode for simple output
response = client.chat.completions.create(
model="deepseek-flash",
messages=[
{"role": "user", "content": "Return one sentence explaining HTTP 404."}
],
extra_body={
"thinking": {
"type": "disabled"
}
},
)
print(checked_text_message(response).content)Enable Thinking Mode intentionally
response = client.chat.completions.create(
model="deepseek-flash",
messages=[
{
"role": "user",
"content": "Explain why database indexes can speed up reads but slow down writes."
}
],
reasoning_effort="high",
extra_body={
"thinking": {
"type": "enabled"
}
},
)
message = checked_text_message(response)
print("Reasoning:")
print(getattr(message, "reasoning_content", None))
print("\nFinal answer:")
print(message.content)When Thinking Mode is enabled, DeepSeek returns reasoning in reasoning_content and the answer in content. Treat them separately. The printout above is for a deliberate local demonstration; avoid placing reasoning traces in ordinary logs or exposing them by default. For mode-specific parameters and replay rules, see our DeepSeek Thinking Mode guide.
Stream DeepSeek responses with the OpenAI SDK
Use streaming to display text as it arrives. Treat displayed fragments as provisional until the stream finishes normally and its final reason is acceptable. These text-only snippets reuse the Python client above and do not execute streamed tool calls.
stream = client.chat.completions.create(
model="deepseek-flash",
messages=[{"role": "user", "content": "Explain recursion in a short example."}],
stream=True,
stream_options={"include_usage": True},
extra_body={"thinking": {"type": "disabled"}},
)
answer_parts = []
finish_reason = None
usage = None
try:
with stream:
for chunk in stream:
if getattr(chunk, "usage", None) is not None:
usage = chunk.usage
for choice in chunk.choices or []:
if choice.index != 0:
raise RuntimeError("Unexpected extra response choice.")
if getattr(choice.delta, "tool_calls", None):
raise RuntimeError("Unexpected tool request in a text-only stream.")
if choice.finish_reason is not None:
finish_reason = choice.finish_reason
text = getattr(choice.delta, "content", None)
if text:
answer_parts.append(text)
print(text, end="", flush=True)
except (Exception, KeyboardInterrupt):
partial_answer = "".join(answer_parts)
print("\nInterrupted; partial text is not a completed answer.")
raise
answer = "".join(answer_parts)
print()
if finish_reason != "stop" or not answer.strip():
raise RuntimeError("Missing, interrupted, or empty final answer; keep text partial.")
if usage is None:
print("Usage unavailable; reconcile billing instead of recording zero cost.")With Thinking Mode enabled, collect delta.reasoning_content separately from delta.content. The first field carries reasoning; the second carries the final answer. This follows DeepSeek’s streaming examples.
stream = client.chat.completions.create(
model="deepseek-flash",
messages=[{"role": "user", "content": "Design a retry strategy for a Python API client."}],
stream=True,
stream_options={"include_usage": True},
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
reasoning_parts = []
answer_parts = []
finish_reason = None
usage = None
try:
with stream:
for chunk in stream:
if getattr(chunk, "usage", None) is not None:
usage = chunk.usage
for choice in chunk.choices or []:
if choice.index != 0:
raise RuntimeError("Unexpected extra response choice.")
if getattr(choice.delta, "tool_calls", None):
raise RuntimeError("Unexpected tool request in a text-only stream.")
if choice.finish_reason is not None:
finish_reason = choice.finish_reason
reasoning_delta = getattr(choice.delta, "reasoning_content", None)
content_delta = getattr(choice.delta, "content", None)
if reasoning_delta:
reasoning_parts.append(reasoning_delta)
if content_delta:
answer_parts.append(content_delta)
print(content_delta, end="", flush=True)
except (Exception, KeyboardInterrupt):
partial_answer = "".join(answer_parts)
print("\nInterrupted; partial text is not a completed answer.")
raise
reasoning_text = "".join(reasoning_parts)
answer = "".join(answer_parts)
print()
if finish_reason != "stop" or not answer.strip():
raise RuntimeError("Missing, interrupted, or empty final answer; keep text partial.")
final_answer = answer
if usage is None:
print("Usage unavailable; reconcile billing instead of recording zero cost.")Do not mix reasoning deltas and answer deltas, and do not trim each fragment. SDK iterators hide the literal [DONE] marker; these examples check iterator completion and finish_reason, not that raw marker. The Chat Completions reference places usage in the last JSON chunk. Missing usage remains unknown; it does not turn an otherwise completed answer into a free request. Raw SSE termination checks, browser cancellation, and durable accounting belong in the dedicated streaming implementation.
Use async Python with DeepSeek
The OpenAI Python SDK provides AsyncOpenAI for asynchronous requests. The DeepSeek configuration and extra_body syntax stay the same; await the API call and close the client when it is no longer needed. See the official async-client documentation.
import asyncio
import os
from openai import AsyncOpenAI
def checked_text_message(response):
choices = getattr(response, "choices", None)
if not isinstance(choices, list) or len(choices) != 1:
raise RuntimeError("Expected exactly one response choice.")
choice = choices[0]
message = getattr(choice, "message", None)
if (getattr(choice, "finish_reason", None) != "stop"
or message is None
or getattr(message, "role", None) != "assistant"
or getattr(message, "tool_calls", None)):
raise RuntimeError("Expected a completed text response; workflow stopped.")
content = getattr(message, "content", None)
if not isinstance(content, str) or not content.strip():
raise RuntimeError("No usable final text was returned.")
return message
async def main():
async with AsyncOpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
max_retries=0,
) as client:
response = await client.chat.completions.create(
model="deepseek-flash",
messages=[
{"role": "user", "content": "Give me three Python API design tips."}
],
extra_body={"thinking": {"type": "disabled"}},
)
print(checked_text_message(response).content)
asyncio.run(main())Use the synchronous client for a simple script and the asynchronous client inside async request handlers. Calling a synchronous network client directly inside an async handler can block that handler’s event loop.
Use the OpenAI JavaScript SDK with DeepSeek
Use the official openai package in a server-side Node.js environment. Its client option is baseURL, not Python’s base_url. The OpenAI JavaScript SDK documentation states that additional request fields are sent as supplied; it does not translate Python’s extra_body wrapper.
npm install openaiSet DEEPSEEK_API_KEY in the same terminal, using the macOS/Linux command below or the PowerShell command shown earlier:
export DEEPSEEK_API_KEY="your-deepseek-api-key"Save this as deepseek-example.mjs. The .mjs extension lets Node.js use the import statement and top-level await in this example, as in the official SDK setup guide.
import OpenAI from "openai";
const apiKey = process.env.DEEPSEEK_API_KEY;
if (!apiKey) {
throw new Error("Set DEEPSEEK_API_KEY before running this script.");
}
const client = new OpenAI({
apiKey,
baseURL: "https://api.deepseek.com",
maxRetries: 0,
});
const completion = await client.chat.completions.create({
model: "deepseek-flash",
messages: [
{ role: "user", content: "Explain what an API base URL does." }
],
thinking: { type: "disabled" }
});
const choice = completion.choices?.[0];
const message = choice?.message;
if (completion.choices?.length !== 1
|| choice?.finish_reason !== "stop"
|| message?.role !== "assistant"
|| message?.tool_calls?.length
|| typeof message?.content !== "string"
|| !message.content.trim()) {
throw new Error("Expected a completed, nonempty text response.");
}
console.log(message.content);Run the file:
node deepseek-example.mjsDo not wrap thinking inside extra_body in JavaScript. The intended HTTP body contains thinking at the top level. In Python, the SDK merges extra_body into that body; in JavaScript, nesting the same wrapper does not perform the Python-specific merge. This distinction follows the Node SDK, Python SDK, and DeepSeek request schema.
TypeScript: type the DeepSeek-specific field
Your installed SDK’s TypeScript definitions may not include DeepSeek’s thinking field. For that case, define a request type that extends the SDK’s non-streaming Chat Completions type. This keeps normal parameter checks without using any or moving the field into extra_body.
import OpenAI from "openai";
import type { ChatCompletionCreateParamsNonStreaming } from
"openai/resources/chat/completions";
type DeepSeekChatRequest = ChatCompletionCreateParamsNonStreaming & {
thinking: { type: "enabled" | "disabled" };
};
const apiKey = process.env.DEEPSEEK_API_KEY;
if (!apiKey) {
throw new Error("Set DEEPSEEK_API_KEY before running this script.");
}
const client = new OpenAI({
apiKey,
baseURL: "https://api.deepseek.com",
maxRetries: 0,
});
const request: DeepSeekChatRequest = {
model: "deepseek-flash",
messages: [
{ role: "user", content: "Explain what an API base URL does." }
],
stream: false,
thinking: { type: "disabled" }
};
const completion = await client.chat.completions.create(request);
const choice = completion.choices?.[0];
const message = choice?.message;
if (completion.choices?.length !== 1
|| choice?.finish_reason !== "stop"
|| message?.role !== "assistant"
|| message?.tool_calls?.length
|| typeof message?.content !== "string"
|| !message.content.trim()) {
throw new Error("Expected a completed, nonempty text response.");
}
console.log(message.content);This is a TypeScript adaptation of the same JavaScript request, not a different API format. Compile it with your project’s TypeScript setup and the types supplied by your pinned openai version. The extension describes a DeepSeek field locally; it does not validate the server’s response. The SDK’s extra-parameter documentation and Chat Completions types explain the underlying client behavior.
JSON output with DeepSeek
DeepSeek supports JSON Output through response_format={"type": "json_object"}. DeepSeek’s JSON Output guide says you should also include the word “json” in the system or user prompt, provide an example of the expected JSON format, and set max_tokens reasonably to avoid truncation. See the DeepSeek JSON Output guide.
import json
import os
from openai import OpenAI
def checked_text_message(response):
choices = getattr(response, "choices", None)
if not isinstance(choices, list) or len(choices) != 1:
raise RuntimeError("Expected exactly one response choice.")
choice = choices[0]
message = getattr(choice, "message", None)
if (getattr(choice, "finish_reason", None) != "stop"
or message is None
or getattr(message, "role", None) != "assistant"
or getattr(message, "tool_calls", None)):
raise RuntimeError("Expected a completed text response; workflow stopped.")
content = getattr(message, "content", None)
if not isinstance(content, str) or not content.strip():
raise RuntimeError("No usable final text was returned.")
return message
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
max_retries=0,
)
response = client.chat.completions.create(
model="deepseek-flash",
messages=[
{
"role": "system",
"content": (
'Return only a JSON object with string fields topic, difficulty, '
'and summary. Example: {"topic":"Python lists",'
'"difficulty":"beginner","summary":"Lists hold ordered items."}'
)
},
{
"role": "user",
"content": (
"Return a JSON object with keys: topic, difficulty, and summary. "
"Topic: Python generators."
)
},
],
response_format={"type": "json_object"},
max_tokens=500,
extra_body={
"thinking": {
"type": "disabled"
}
},
)
message = checked_text_message(response)
try:
data = json.loads(message.content)
except json.JSONDecodeError as exc:
raise ValueError("The completed response did not contain valid JSON.") from exc
expected_keys = {"topic", "difficulty", "summary"}
if not isinstance(data, dict) or set(data) != expected_keys:
raise ValueError("Unexpected JSON fields.")
if not all(isinstance(value, str) for value in data.values()):
raise ValueError("All JSON fields must be strings.")
print(data)JSON Output controls formatting; it does not execute application functions or validate business rules. The example checks completion before parsing, then checks the decoded object and field types. Handle parse or validation failures explicitly. Our DeepSeek JSON Output guide covers the structured-output workflow in more detail.
Tool calls with DeepSeek
For the custom Chat Completions functions shown here, the model requests a function and your application executes it. Validate the response state, allowed function, arguments, and permissions before execution. This statement does not describe every provider-hosted tool on other API routes. See the official Tool Calls guide.
Save this complete example as deepseek_sdk_tools.py. It uses a mock weather function with fixed fictional values, not a live service. It accepts one batch of up to four calls, validates the entire batch before execution, then requests final text with tool_choice="none". Four calls and the 1,024-token output cap are tutorial settings, not provider limits. Thinking is disabled explicitly.
For the full request-and-result workflow, see our DeepSeek Tool Calls guide.
Check completion before parsing arguments. Valid JSON does not make a length, content_filter, insufficient_system_resource, or aborted result usable for execution. The helper also rejects missing or unknown stop reasons. It requires tool_calls when a batch is present, and stop with nonblank text and no tool requests for the final answer. These meanings come from the Chat Completions response schema.
"""One tool batch with mock weather data; live model calls require a DeepSeek key."""
import json
import os
from openai import OpenAI
# Application-level demo cap, not a DeepSeek API limit.
MAX_TOOL_CALLS = 4
TOOLS = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Return fixed demonstration weather data, not live weather.",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
"additionalProperties": False,
},
},
}]
def checked_choice(response):
choices = getattr(response, "choices", None)
if not isinstance(choices, list) or len(choices) != 1:
raise RuntimeError("Expected exactly one response choice; workflow stopped.")
choice = choices[0]
if getattr(choice, "finish_reason", None) not in ("stop", "tool_calls"):
raise RuntimeError("Incomplete or interrupted response; workflow stopped.")
message = getattr(choice, "message", None)
if message is None or getattr(message, "role", None) != "assistant":
raise RuntimeError("Missing or invalid assistant message; workflow stopped.")
return choice
def completed_text(choice) -> str:
message = choice.message
if choice.finish_reason != "stop" or getattr(message, "tool_calls", None):
raise RuntimeError("Expected final text, not another tool request.")
content = getattr(message, "content", None)
if not isinstance(content, str) or not content.strip():
raise RuntimeError("No usable final text was returned.")
return content
def validate_tool_batch(calls) -> list[tuple[str, str]]:
if not isinstance(calls, list) or not 1 <= len(calls) <= MAX_TOOL_CALLS:
raise ValueError("Unexpected tool batch size; no tools were executed.")
prepared = []
seen_ids = set()
for call in calls:
call_id = getattr(call, "id", None)
if not isinstance(call_id, str) or not call_id.strip():
raise ValueError("Missing tool call ID; no tools were executed.")
if call_id in seen_ids:
raise ValueError("Duplicate tool call ID; no tools were executed.")
seen_ids.add(call_id)
function = getattr(call, "function", None)
if (getattr(call, "type", None) != "function"
or getattr(function, "name", None) != "get_weather"):
raise ValueError("Unsupported tool; no tools were executed.")
raw = getattr(function, "arguments", None)
if not isinstance(raw, str):
raise ValueError("Tool arguments must be a JSON string.")
try:
arguments = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError("Invalid tool JSON; no tools were executed.") from exc
if not isinstance(arguments, dict) or set(arguments) != {"location"}:
raise ValueError("Unexpected argument fields; no tools were executed.")
location = arguments["location"]
if not isinstance(location, str) or not location.strip():
raise ValueError("Invalid location; no tools were executed.")
prepared.append((call_id, location.strip()))
return prepared
def get_weather(location: str) -> dict:
# Fixed fictional values, NOT a live weather observation or forecast.
return {
"is_mock": True,
"location": location,
"temperature_f": 72,
"condition": "clear",
}
def run_weather_demo(client) -> str:
messages = [
{"role": "system", "content": (
"Use the tool when needed. Its weather values are fictional demo data. "
"Label them as mock data; never describe them as current weather."
)},
{"role": "user", "content": "Show sample weather data for Austin, Texas."},
]
options = {
"model": "deepseek-flash",
"tools": TOOLS,
"max_tokens": 1024,
"extra_body": {"thinking": {"type": "disabled"}},
}
first = checked_choice(client.chat.completions.create(
messages=messages, tool_choice="auto", **options
))
calls = getattr(first.message, "tool_calls", None)
if not calls:
# auto may return text. Do not invent a tool result or force a retry.
return completed_text(first)
if first.finish_reason != "tool_calls":
raise RuntimeError("Tool calls conflict with finish_reason; none executed.")
# Validate the entire batch BEFORE invoking even the first mock function.
prepared = validate_tool_batch(calls)
messages.append(first.message)
for call_id, location in prepared:
result = get_weather(location)
messages.append({
"role": "tool",
"tool_call_id": call_id,
"content": json.dumps(result),
})
final = checked_choice(client.chat.completions.create(
messages=messages, tool_choice="none", **options
))
# An incomplete final response does not undo any earlier tool execution.
return completed_text(final)
if __name__ == "__main__":
api_key = os.environ.get("DEEPSEEK_API_KEY")
if not api_key:
raise SystemExit("Set DEEPSEEK_API_KEY before running this example.")
with OpenAI(
api_key=api_key,
base_url="https://api.deepseek.com",
max_retries=0,
) as client:
print("DEMO ONLY: all weather values are fictional, not live observations.")
print(run_weather_demo(client))Run python deepseek_sdk_tools.py after installing the SDK and setting your key. With auto, a completed text answer without a tool request is allowed and produces no lookup. An invalid call or an inconsistent batch stops the demonstration before any mock function runs.
What this example checks before continuing
| Response or batch condition | Action in this example |
|---|---|
Completed tool_calls response with valid IDs and arguments | Execute the validated mock batch, then request final text. |
| Interrupted or truncated first response, even with parseable JSON | Stop before invoking any function. |
| Unknown function, invalid fields, duplicate ID, or too many calls | Reject the whole batch before execution. |
stop with no tools and usable text | Accept the text path; do not invent a lookup. |
| Final response is truncated, empty, or requests another tool | Reject it as a completed answer; do not automatically replay the batch. |
This is not a transaction or an authorization system. If the final model call fails, previously executed functions are not undone. A real integration needs record-level access checks, persisted execution results, and duplicate-execution protection before retrying actions. The demo makes no live weather request and never evaluates a model-generated function name as code.
For Thinking Mode requests that carry tools, preserve reasoning_content from all previous assistant turns, including turns without a tool call. Keeping the full returned assistant message helps preserve the required fields. Also, tool_choice="required" and a forced named function require Thinking Mode to be disabled. See the reasoning-history rules and tool-choice restrictions.
Using the Responses API with DeepSeek
DeepSeek also supports the Responses API format with the same SDK package and provider base URL. Call client.responses.create(), then check its status and output rather than looking for Chat Completions finish_reason. This text-only example disables thinking through reasoning={"effort": "none"}, as defined by the DeepSeek Responses API reference.
import os
from openai import OpenAI
with OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
max_retries=0,
) as client:
response = client.responses.create(
model="deepseek-flash",
instructions="You are a helpful assistant.",
input="Give me one tip for writing reliable API clients.",
reasoning={"effort": "none"},
)
if (getattr(response, "status", None) != "completed"
or getattr(response, "error", None) is not None
or getattr(response, "incomplete_details", None) is not None):
raise RuntimeError("The Responses request did not complete normally.")
items = getattr(response, "output", None)
if not isinstance(items, list) or not items:
raise RuntimeError("No response output was returned.")
for item in items:
if getattr(item, "type", None) not in ("message", "reasoning"):
raise RuntimeError("Unexpected non-text output in this text-only example.")
if getattr(item, "status", None) in ("in_progress", "incomplete"):
raise RuntimeError("An output item is not complete.")
answer = getattr(response, "output_text", None)
if not isinstance(answer, str) or not answer.strip():
raise RuntimeError("No usable final text was returned.")
print(answer)Do not assume every OpenAI Responses feature works with DeepSeek. Its implementation is stateless, so a multi-turn client must supply the needed history rather than rely on provider-hosted conversation state. Responses also has different output items and streaming events; do not use the Chat Completions parser above for it. Consult the provider’s Responses contract before porting advanced examples.
What works differently from OpenAI?
The OpenAI SDK can send compatible requests, but DeepSeek is still a different provider with its own models, pricing, parameters, and feature support.
| Assumption | Correct DeepSeek-specific view |
|---|---|
| “I can use my OpenAI API key.” | No. Use a DeepSeek API key. |
| “The default OpenAI endpoint is fine.” | No. Set base_url="https://api.deepseek.com" in Python or baseURL: "https://api.deepseek.com" in JavaScript. |
| “OpenAI model names work.” | No. Use DeepSeek model IDs such as deepseek-flash. |
| “Every OpenAI feature works.” | No. Use only features DeepSeek documents for its own API. |
| “Thinking Mode behaves like OpenAI reasoning.” | No. Follow DeepSeek’s own thinking, reasoning_effort, and reasoning_content documentation. |
| “Tool calls are automatically safe.” | No. Your application must allowlist functions and validate arguments. |
| “I am billed by OpenAI because I installed the OpenAI SDK.” | No. When the request goes to DeepSeek’s base URL with a DeepSeek key, DeepSeek is the provider. |
| “Python and JavaScript pass DeepSeek-only fields the same way.” | No. Python uses extra_body={"thinking": ...}; JavaScript uses top-level thinking. TypeScript may need a local type extension. |
| “Valid tool JSON means the call is ready to run.” | No. First verify response completion and stop-reason consistency; then validate the entire batch and authorize execution. |
Common errors and fixes
| Problem | Likely cause | Fix |
|---|---|---|
ModuleNotFoundError: No module named 'openai' | The OpenAI Python package is not installed in the active environment. | Run pip install openai in the same virtual environment that runs your script. |
| Authentication failure | Missing or incorrect DeepSeek API key. | Set DEEPSEEK_API_KEY and confirm your process can read it. |
| Request goes to OpenAI instead of DeepSeek | The DeepSeek base URL was not configured correctly. | Set base_url="https://api.deepseek.com" in Python or baseURL: "https://api.deepseek.com" in JavaScript. |
| Model not found | The code uses an OpenAI model name or an outdated DeepSeek model name. | Use current DeepSeek model IDs from the official Models & Pricing page. |
| Unexpected reasoning output | Thinking Mode is enabled by default. | Disable thinking for simple calls or handle reasoning_content separately. |
| Streaming prints nothing | The code reads the wrong delta field or assumes every chunk has content. | Check chunk.choices, then read delta.content for normal answer text. |
| JSON parsing fails | The response is empty, malformed, or truncated. | Use response_format, explicitly ask for JSON, provide a JSON example, allow enough max_tokens, and validate the completed response. |
| Tool result is not connected to the request | The tool result was not appended with the correct tool_call_id. | Append a role: "tool" message using the original tool call ID, then send the next request. |
| Tool call returns a 400 error in Thinking Mode | Reasoning history was dropped, or unsupported tool_choice behavior was used. | Restore the full assistant history when reasoning fields are missing; separately, disable Thinking Mode before required or named tool choice. |
| Thinking is not configured as intended in JavaScript | Python’s extra_body wrapper was copied into the Node request. | Place thinking: { type: "disabled" } directly beside model and messages. |
TypeScript rejects the thinking field | The installed SDK type does not declare this provider-specific parameter. | Use the explicit request-type extension above. A local type error does not by itself mean DeepSeek rejects the field. |
import or top-level await fails in Node.js | The file is being interpreted outside an ES module setup. | Run the example as deepseek-example.mjs, or configure ES modules in your project. |
Tool JSON parses, but the response ended with length or an interruption | Readable arguments arrived before a usable completion state. | Stop before execution. Review the output budget or interruption; do not reconstruct or execute a partial call. |
| A final answer is cut off after a successful tool lookup | The follow-up model response is incomplete. | Keep the tool result and reject the partial answer. Do not replay an action blindly to regenerate the wording. |
| Missing or duplicate tool call IDs | The batch cannot be paired reliably with tool results. | Reject the batch before execution; never manufacture replacement IDs. |
| Stream ends without an acceptable finish reason | The response did not establish a completed text answer. | Keep visible text marked partial and handle recovery explicitly. Missing usage is a separate accounting issue. |
For provider HTTP failures, use the DeepSeek API error-code guide. An HTTP error and an incomplete generation returned inside an HTTP 200 response are different conditions; both need handling.
Production checklist
- Use the official OpenAI SDK package only as the client library.
- Use a DeepSeek API key, not an OpenAI API key.
- Set
base_url="https://api.deepseek.com"in Python. - Set
baseURL: "https://api.deepseek.com"in JavaScript. - Use current DeepSeek model IDs such as
deepseek-flash. - Decide whether each request should use Thinking Mode or non-thinking mode.
- Keep
reasoning_contentseparate from finalcontent. - Check DeepSeek docs before relying on OpenAI-only features.
- Check response completion and stop-reason consistency before validating tool-call IDs, names, and arguments; authorize real actions separately.
- Store API keys in environment variables or a secrets manager.
- Do not commit keys to Git or paste them into notebooks, screenshots, or logs.
- Add timeouts, bounded retries, and error logging appropriate for your backend.
- Monitor token usage and costs through DeepSeek’s billing and response usage fields.
- Pin SDK versions in production when stability matters.
- Rotate any key that may have leaked.
- Pass DeepSeek-only fields through
extra_bodyin Python, but directly in the JavaScript request object. - Type-check TypeScript integrations against the pinned SDK, and inspect serialized request bodies without exposing credentials.
- Keep mock tool data clearly labeled and replace it with an approved data source before production use.
- Reject incomplete final answers, and retain completed tool results so a recovery attempt does not duplicate an action.
- Test missing choices, malformed batches, interruptions, no-tool responses, and truncated final text before deployment.
Frequently asked questions
Can I use the OpenAI SDK with DeepSeek?
Yes. DeepSeek’s official Python examples use the OpenAI SDK with DeepSeek’s API key, DeepSeek’s base_url, and a DeepSeek model name such as deepseek-flash.
What base_url should I use for DeepSeek?
Use https://api.deepseek.com for the OpenAI-compatible DeepSeek API format.
Do I use an OpenAI API key or a DeepSeek API key?
Use a DeepSeek API key. The OpenAI SDK is only the client package in this setup.
What package do I install?
For Python, install openai with pip install openai. For JavaScript or TypeScript, install openai with npm install openai.
What model name should I use?
For most new examples, start with deepseek-flash. Check DeepSeek’s Models & Pricing page before production deployment because model names and routing can change.
Does streaming work with DeepSeek and the OpenAI SDK?
Yes. Use stream=True with Chat Completions and iterate over chunks. Read delta.content for normal answer text, and handle delta.reasoning_content separately when streaming Thinking Mode responses.
Does async Python work?
Yes. Use AsyncOpenAI, await the API request, and keep the same DeepSeek key and base_url. Python’s extra_body syntax also applies to the async client.
Does JSON output work?
Yes. DeepSeek documents JSON Output with response_format={"type": "json_object"}, but you should also instruct the model to output JSON and provide a clear example.
Do tool calls work?
Yes. For custom Chat Completions functions, your application must check the response state, validate call IDs, names and arguments, and enforce access rules before execution. After sending results back, check the final response too; valid function arguments alone do not prove completion.
Can I execute a tool call when finish_reason is length?
No. This guide stops the workflow before execution when generation is truncated or interrupted, even if the argument string happens to parse as JSON. Inspect the response state first, then the schema and execution permissions.
Is DeepSeek fully compatible with OpenAI?
No provider should be treated as automatically compatible with every OpenAI feature. DeepSeek supports OpenAI-compatible API formats for documented endpoints and features, but DeepSeek’s documentation is the source of truth for what works on DeepSeek.
Am I billed by OpenAI or DeepSeek?
If your client uses a DeepSeek API key and base_url="https://api.deepseek.com", your request goes to DeepSeek and is billed by DeepSeek. Installing the OpenAI SDK does not make the request an OpenAI-billed request.
Why is my request going to OpenAI instead of DeepSeek?
Check whether you configured the DeepSeek base URL correctly. In Python, use base_url. In JavaScript, use baseURL.
Should I use extra_body in JavaScript?
No. For the Chat Completions examples here, place thinking directly in the JavaScript request object. Keep extra_body in Python, where it is an SDK option for adding fields to the HTTP body.
Why does TypeScript complain about thinking?
The SDK’s types describe the OpenAI API and may not declare DeepSeek-specific fields. Define an explicit request type that includes thinking, while keeping it at the top level of the object sent to DeepSeek.
Bottom line
The OpenAI SDK works with DeepSeek because DeepSeek exposes an OpenAI-compatible API format. For the basic setup, install openai, store your DeepSeek key in DEEPSEEK_API_KEY, set base_url="https://api.deepseek.com" in Python or baseURL: "https://api.deepseek.com" in JavaScript, and use a DeepSeek model such as deepseek-flash.
Remember the three provider settings: DeepSeek key, DeepSeek base URL, DeepSeek model name. Then use the correct SDK syntax and verify completion before using output. For custom tools, validate the complete batch before execution and check the final response separately. Add streaming, async calls, JSON Output, or Tool Calls only where your application needs them.
Official references
- DeepSeek chat Python sample
- DeepSeek Models & Pricing
- DeepSeek Chat Completions API reference
- DeepSeek Thinking Mode guide
- DeepSeek JSON Output guide
- DeepSeek Tool Calls guide
- DeepSeek Responses API guide
- OpenAI SDK documentation
- OpenAI Python API library reference
- OpenAI TypeScript and JavaScript SDK
- OpenAI Python SDK: extra request parameters
- OpenAI JavaScript SDK: extra request parameters
- DeepSeek Responses API request and response reference
