DeepSeek Python Integration with V4: A Practical Tutorial

You have a DeepSeek API key, an existing Python project, and a question that gets in the way of every first call: do you really need a new SDK, or does the OpenAI client already cover it? This tutorial answers that, then walks through a complete DeepSeek Python integration against the two current API models, deepseek-flash (served by DeepSeek-V4.1-Flash-1017) and deepseek-v4-pro (served by DeepSeek-V4-Pro-0813). By the end you will have a working client, a streaming example, a JSON-mode call that does not silently truncate, a thinking-mode pattern, and a cost calculation you can paste into a finance review. Every snippet is written for Python 3.10 or later with the current openai package and uses only the fields DeepSeek documents.
What you will build

You will build a small Python module that talks to the DeepSeek API using the official OpenAI SDK — no DeepSeek-specific package needed. The module will support both current models, switch between thinking and non-thinking mode with a single argument, stream tokens, request structured JSON, and log token usage so you can keep an eye on spend.
If you are migrating older code: the legacy deepseek-chat and deepseek-reasoner IDs were scheduled for retirement on 2026-07-24 at 15:59 UTC and are no longer listed in DeepSeek’s docs, and the legacy names deepseek-v4-flash and deepseek-v4-flash-vision-exp are still accepted but served by DeepSeek-V4.1-Flash-1017. New code should target deepseek-flash or deepseek-v4-pro from day one. If you have not picked up an API key yet, the get a DeepSeek API key walkthrough covers the platform.deepseek.com signup and the first top-up.
Prerequisites
- Python 3.10+ — the minimum the current
openaipackage supports. - OpenAI Python SDK, a current release:
pip install -U openai. Version 3.37.0 was the latest on PyPI on 24 October 2026; the parameters used below (reasoning_effort,extra_body,response_format,tools,stream) are all part of itschat.completions.create()signature. - A DeepSeek API key with a topped-up or granted balance — calls return HTTP 402 “Insufficient Balance” if the account has run out.
- An environment variable
DEEPSEEK_API_KEY. Do not paste keys into source files. - Optional but recommended:
python-dotenvfor local development. The SDK brings its own HTTP client, so nothing else is required.
Why the OpenAI SDK works
DeepSeek’s docs put it this way: “The DeepSeek API uses an API format compatible with OpenAI/Anthropic. By modifying the configuration, you can use the OpenAI/Anthropic SDK or softwares compatible with the OpenAI/Anthropic API to access the DeepSeek API.” In practice that means swapping two values — base_url and api_key — and pointing the model field at a DeepSeek ID. Chat requests hit POST /chat/completions, the OpenAI-compatible endpoint, with the same JSON body shape you already know. The full reference lives in the DeepSeek OpenAI SDK compatibility guide.
If you prefer the Anthropic SDK, that works too, at a separate base URL: https://api.deepseek.com/anthropic. This article sticks with the OpenAI client because most existing Python codebases already depend on it.
Step 1 — Install the SDK and set environment variables
Create a virtual environment and install the OpenAI client. The shell commands below are bash-flavoured; PowerShell users should use $env:DEEPSEEK_API_KEY syntax.
python -m venv .venv
source .venv/bin/activate
pip install -U openai python-dotenv
export DEEPSEEK_API_KEY="sk-..."
For project-level config, put the key in a .env file at the repo root, add .env to .gitignore, and call load_dotenv() (from dotenv) before you create the client. The DeepSeek API authentication guide covers key rotation and revocation in more detail.
Step 2 — Your first DeepSeek Python integration call

Save the following Python script as hello_deepseek.py. It targets deepseek-flash, the cost-efficient default for most workloads, with thinking mode switched on explicitly as in DeepSeek’s “Your First API Call” example.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
)
response = client.chat.completions.create(
model="deepseek-flash",
messages=[
{"role": "system", "content": "You are a precise technical assistant."},
{"role": "user", "content": "In one sentence, what is a Mixture-of-Experts model?"},
],
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
print(response.choices[0].message.content)
print("usage:", response.usage)
Run it with python hello_deepseek.py. You should see a one-sentence answer plus a usage block listing prompt, completion and total tokens.
Picking deepseek-flash vs deepseek-v4-pro
The two model IDs share the same API surface but differ in price and features. Both support a 1M-token context, thinking and non-thinking mode, JSON output and tool calls; only deepseek-flash accepts images.
| Field | deepseek-flash | deepseek-v4-pro |
|---|---|---|
| Served by | DeepSeek-V4.1-Flash-1017 | DeepSeek-V4-Pro-0813 |
| Context | 1M tokens | 1M tokens |
| Max output | 384K tokens | 384K tokens |
| Vision (image input) | Yes | No |
| Concurrency limit (per account) | 2,500 | 500 |
| Input, cache hit, per 1M (peak / off-peak) | $0.006 / $0.003 | $0.044 / $0.022 |
| Input, cache miss, per 1M (peak / off-peak) | $0.30 / $0.15 | $1.32 / $0.66 |
| Output, per 1M (peak / off-peak) | $1.20 / $0.60 | $3.96 / $1.98 |
| Best for | Default choice: chat, classification, RAG, anything with images | Workloads where you have measured a quality gain worth the higher price |
Pricing as of October 2026, per the official DeepSeek pricing page. Off-peak rates are half the peak rates; peak hours are 01:00–04:00 and 06:00–10:00 UTC, Monday to Friday, excluding Chinese public holidays, and all other hours are off-peak. deepseek-v4-pro costs 3.3× deepseek-flash on output and 4.4× on cache-miss input, so default to Flash and only escalate when you have a measured quality lift.
Step 3 — Multi-turn conversations (the API is stateless)
This trips up many developers moving from the chat app to the API. The web chat at chat.deepseek.com keeps your history for you. The API does not. You must resend the full messages array on every request. This loop runs in non-thinking mode so that the temperature setting takes effect:
def chat_loop():
history = [{"role": "system", "content": "You are a precise technical assistant."}]
while True:
user_input = input("you: ").strip()
if not user_input:
break
history.append({"role": "user", "content": user_input})
resp = client.chat.completions.create(
model="deepseek-flash",
messages=history,
temperature=1.3, # general conversation
max_tokens=800,
extra_body={"thinking": {"type": "disabled"}},
)
reply = resp.choices[0].message.content
history.append({"role": "assistant", "content": reply})
print(f"deepseek: {reply}\n")
Two practical notes. First, history grows on every turn — once it threatens to exceed your budget, summarise older turns into a single system message. Second, the temperature recommendations DeepSeek publishes are: 0.0 for code and maths, 1.0 for data analysis, 1.3 for general chat and translation, 1.5 for creative writing. They apply to non-thinking mode only: thinking mode (the default) accepts temperature without error but ignores it. In thinking mode without tools, you do not need to send reasoning_content back in the history; DeepSeek’s docs say it is ignored if you do.
Step 4 — Streaming responses
For anything user-facing, stream. It cuts perceived latency from “the model is broken” to “the model is fast” without any actual speedup. In thinking mode the reasoning arrives first, on delta.reasoning_content, followed by the answer on delta.content:
stream = client.chat.completions.create(
model="deepseek-flash",
messages=[{"role": "user", "content": "Explain context caching in three short paragraphs."}],
stream=True,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if getattr(delta, "reasoning_content", None):
pass # reasoning tokens; log or display them separately if you need them
elif delta.content:
print(delta.content, end="", flush=True)
print()
Server-sent events arrive as ChatCompletionChunk objects. Catch openai.APIConnectionError around the iterator if you are running over a flaky network — broken streams should not crash your worker. The DeepSeek API streaming reference walks through SSE framing and reconnection patterns.
Step 5 — Thinking mode
Thinking mode is a request parameter on either model, not a separate model ID, and it is on by default with effort high. You control it with two fields: extra_body={"thinking": {"type": "enabled"}} or {"type": "disabled"}, and reasoning_effort set to "low", "high" or "max". The model returns reasoning_content alongside the final content when thinking is on:
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "user", "content": "A train leaves Chicago at 60mph; another leaves Denver at 75mph. They start 1,000 miles apart. When do they meet?"},
],
reasoning_effort="max",
extra_body={"thinking": {"type": "enabled"}},
)
msg = response.choices[0].message
print("THINKING:", getattr(msg, "reasoning_content", None))
print("ANSWER:", msg.content)
DeepSeek’s 13 August 2026 guidance is low for simple tasks, high for daily agent tasks and max for more complex scenarios; other requested values are mapped (minimal → low, medium and xhigh → high). To opt out — for latency-sensitive calls or FIM completion — pass extra_body={"thinking": {"type": "disabled"}}. Avoid a small max_tokens in thinking mode; if a response stops early, check for finish_reason == "length". Code that still uses the old deepseek-reasoner ID needs updating: that ID was scheduled for retirement on 2026-07-24 at 15:59 UTC, and the replacement is deepseek-flash or deepseek-v4-pro with thinking enabled.
Step 6 — JSON mode without footguns
JSON mode is designed to return valid JSON, not guaranteed to. The API can occasionally return empty content, or content that was truncated mid-object because max_tokens was too low. Three rules keep it boring:
- Set
response_format={"type": "json_object"}. - Include the word json in the system or user prompt, with a small example schema.
- Set
max_tokenshigh enough that the response cannot truncate.
import json
schema_hint = """
Return JSON in this exact shape:
{"sentiment": "positive|negative|neutral", "confidence": 0.0-1.0, "key_phrases": ["..."]}
"""
resp = client.chat.completions.create(
model="deepseek-flash",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "You are a sentiment classifier. Respond only with JSON. " + schema_hint},
{"role": "user", "content": "The dashboard finally loads in under a second. Impressive."},
],
temperature=0.0,
max_tokens=400,
extra_body={"thinking": {"type": "disabled"}},
)
raw = resp.choices[0].message.content
if not raw:
raise RuntimeError("Empty JSON response — retry or fall back.")
result = json.loads(raw)
print(result)
This classifier runs in non-thinking mode so that temperature=0.0 applies. For deeper patterns — including retry-on-empty and Pydantic validation — see the DeepSeek API JSON mode reference.
Step 7 — Tool calling
Tool calling uses the OpenAI tools array verbatim, on both models and in both thinking and non-thinking mode. One DeepSeek-specific rule matters in thinking mode: when a request carries tools, the reasoning_content of all previous assistant turns must be passed back, or the API returns a 400 error. Appending the returned message object, as below, does that for you.
import json
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
def get_weather(city):
return f"Cloudy, 12°C in {city}" # replace with a real lookup
messages = [{"role": "user", "content": "What's the weather in Dublin?"}]
while True:
resp = client.chat.completions.create(
model="deepseek-flash",
messages=messages,
tools=tools,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
msg = resp.choices[0].message
messages.append(msg) # keeps content, reasoning_content and tool_calls
if not msg.tool_calls:
print(msg.content)
break
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": get_weather(**args),
})
The model returns a tool_calls entry; your code executes the function, appends a {"role": "tool", ...} message with the result, and calls the API again until the model answers without a tool call. The DeepSeek API function calling guide has a full agent loop example.
Step 8 — Verify it worked

Run a smoke test that exercises every path you rely on before you deploy. A sensible minimum:
- A non-streaming call returns a non-empty
contentand ausageblock with non-zero token counts. - A streaming call yields at least one chunk with non-empty
delta.content. - A JSON-mode call parses cleanly through
json.loads. - A thinking-mode call exposes
reasoning_contenton the message object. - An invalid API key surfaces
openai.AuthenticationErrorrather than crashing.
Common errors and fixes
| Error | Likely cause | Fix |
|---|---|---|
401 Authentication Fails |
Wrong or missing key | Re-export DEEPSEEK_API_KEY; rotate if leaked. |
402 Insufficient Balance |
Account balance used up | Top up at platform.deepseek.com; the key is fine. |
429 Rate Limit Reached |
Requests too fast, or over the concurrency limit (2,500 for deepseek-flash, 500 for deepseek-v4-pro, per account) |
Exponential backoff; cap concurrency. |
400 in a tool-calling loop |
Thinking mode with tools, but earlier reasoning_content not passed back |
Append the full returned message to messages. |
Empty content in JSON mode |
Prompt missing “json” / truncation | Add schema example; raise max_tokens. |
| Truncated answer mid-sentence | max_tokens too low |
Raise it; check finish_reason == "length". |
| Calls with an old model ID | deepseek-chat / deepseek-reasoner were scheduled for retirement on 2026-07-24 and are no longer listed |
Switch to deepseek-flash or deepseek-v4-pro. |
TypeError: unexpected keyword argument reasoning_effort |
Very old openai package |
Upgrade with pip install -U openai. |
The DeepSeek API error codes reference lists every status code with retry guidance.
Costing your integration honestly
Every Python integration eventually faces the question “what does this cost at 1M calls a day?” Here is the worked example for deepseek-flash with a 2,000-token system prompt that gets cached, a 200-token user message that does not, and a 300-token reply, at the rates above:
- Cached input: 2,000 × 1,000,000 = 2,000,000,000 tokens × $0.006/M = $12.00 peak; × $0.003/M = $6.00 off-peak
- Uncached input: 200 × 1,000,000 = 200,000,000 tokens × $0.30/M = $60.00 peak; × $0.15/M = $30.00 off-peak
- Output: 300 × 1,000,000 = 300,000,000 tokens × $1.20/M = $360.00 peak; × $0.60/M = $180.00 off-peak
- Total per 1M calls: $432.00 if run entirely in peak hours; $216.00 if run entirely off-peak
The same workload on deepseek-v4-pro costs $88.00 + $264.00 + $1,188.00 = $1,540.00 at peak, or $44.00 + $132.00 + $594.00 = $770.00 off-peak — about 3.6× the Flash total. A real workload usually straddles both periods, so weight the two totals by the share of traffic you expect in each. The trap is forgetting the uncached-input line: each new user message is a cache miss against the cached prefix, even when the system prompt is reused. Skip that line and you under-budget by about 14% on Flash ($60.00 of $432.00) and about 17% on Pro ($264.00 of $1,540.00).
Two mechanical levers reduce the bill: keep system prompts stable so the cache hit rate stays high (see DeepSeek context caching), and schedule batch work for off-peak hours, when every rate is half price. Setting max_tokens to the output you actually need also caps runaway responses; the 384K maximum is for niche long-form work.
Production hardening
Three habits separate prototypes from running services:
- Wrap every call in a retry helper that handles 429 and 5xx with exponential backoff. The OpenAI SDK already retries timeouts, 429 and 5xx responses twice by default (
max_retrieson the client); raise that or add your own backoff for batch jobs. Do not retry other 4xx errors — they are bugs in your code, not transient failures. - Log token usage on every call. Ship the
usageobject to your observability stack: DeepSeek’s API reference documentsprompt_tokens,completion_tokens, the cache splitprompt_cache_hit_tokens/prompt_cache_miss_tokens, andcompletion_tokens_details.reasoning_tokens. An alert on a sudden spike in reasoning tokens catches drifted prompts before the bill arrives. - Cap concurrency. DeepSeek counts concurrency per account — 2,500 requests for
deepseek-flashand 500 fordeepseek-v4-pro— and returns HTTP 429 above the limit. Anasyncio.Semaphorearound theAsyncOpenAIclient keeps a runaway worker inside it.
For framework integrations — agent loops, document Q&A, vector retrieval — there are dedicated tutorials for DeepSeek with LangChain and DeepSeek with LlamaIndex.
Next steps

Once the basic integration is solid, the highest-use follow-ups are: deploying behind a Streamlit or FastAPI front-end, adding retrieval, or migrating an existing OpenAI codebase. Try the DeepSeek RAG tutorial for retrieval, the DeepSeek Streamlit app walkthrough for a quick UI, or browse the full set of DeepSeek tutorials for adjacent topics. If your codebase is JavaScript-first, the parallel DeepSeek Node.js integration guide mirrors this one.
Frequently asked questions
How do you install the DeepSeek Python SDK?
There is no separate DeepSeek Python SDK. DeepSeek’s API is OpenAI-compatible, so you install the OpenAI client with pip install -U openai and point it at https://api.deepseek.com with your DeepSeek API key. The Anthropic SDK also works if you point it at https://api.deepseek.com/anthropic. The DeepSeek SDKs reference covers every supported client.
What model ID should you use in Python?
Use deepseek-flash (DeepSeek-V4.1-Flash-1017) for chat, classification, image input and most workloads, or deepseek-v4-pro (DeepSeek-V4-Pro-0813) where you have measured a quality gain worth its higher price. The legacy deepseek-chat and deepseek-reasoner IDs were scheduled for retirement on 2026-07-24 at 15:59 UTC and are no longer listed in the docs. See the DeepSeek V4 model page for the full lineage.
Does DeepSeek work with the OpenAI Python client without code changes?
Almost — you change two values. Set base_url="https://api.deepseek.com" on the OpenAI() constructor and pass your DeepSeek API key. The wire format is the same for chat completions, streaming, JSON mode, and tool calling, and reasoning_effort is a regular argument of the SDK’s create() method. Only DeepSeek-specific fields such as the thinking toggle (and user_id) go in the extra_body argument. The DeepSeek OpenAI SDK compatibility guide details every edge case.
How do you enable thinking mode from Python?
Thinking mode is on by default with effort high. To set it explicitly, pass reasoning_effort="high" (or "low" / "max") together with extra_body={"thinking": {"type": "enabled"}} on either model; to turn it off, use {"type": "disabled"}. With thinking on, the response object exposes reasoning_content alongside the final content. The DeepSeek API best practices guide covers when each effort level is worth the latency cost.
Can you stream tokens with the DeepSeek Python integration?
Yes. Set stream=True on client.chat.completions.create() and iterate the returned object — each chunk is a ChatCompletionChunk with a delta. When thinking mode is enabled, reasoning streams on delta.reasoning_content before the answer arrives on delta.content. Wrap the iterator in try/except for APIConnectionError so flaky networks do not crash your worker. The DeepSeek API streaming reference walks through reconnection patterns and SSE framing.
Sources & Methodology
Primary sources
- PricingDeepSeek API Docs — Models & PricingPeak and off-peak rates for deepseek-flash and deepseek-v4-pro, peak hours, 1M context, 384K output, vision on deepseek-flash only; accessed 24 Oct 2026Last checked: October 24, 2026
- OfficialDeepSeek API Docs — Your First API CallOpenAI/Anthropic base URLs, model names, thinking fields in the Python example; accessed 24 Oct 2026Last checked: October 24, 2026
- OfficialDeepSeek API Docs — Thinking ModeThinking on by default (effort high), extra_body thinking toggle, effort mapping, temperature ignored in thinking mode, reasoning_content handling in multi-turn and tool calls (400 if not passed back); accessed 24 Oct 2026Last checked: October 24, 2026
Technical references
- OfficialDeepSeek API Docs — Rate Limit & IsolationConcurrency limits 2,500 / 500 per account, HTTP 429, user_id via extra_body; accessed 24 Oct 2026Last checked: October 24, 2026
- OfficialDeepSeek API Docs — Error Codes401, 402, 429 causes and fixes; accessed 24 Oct 2026Last checked: October 24, 2026
- SDKopenai on PyPI (openai-python 3.37.0)Current version, Requires-Python >=3.10, chat.completions.create() parameters, default max_retries 2 (timeouts, 429, 5xx); accessed 24 Oct 2026Last checked: October 24, 2026
- OfficialDeepSeek API Docs — Create Chat Completionusage fields (prompt_cache_hit_tokens, prompt_cache_miss_tokens, completion_tokens_details.reasoning_tokens), finish_reason 'length'; accessed 24 Oct 2026Last checked: October 24, 2026
Context sources
- OfficialDeepSeek API Docs — Change LogModel versions behind deepseek-flash / deepseek-v4-pro, legacy names, 13 Aug 2026 effort guidance; accessed 24 Oct 2026Last checked: October 24, 2026
Methodology
Information in this article was checked against the official DeepSeek documentation, the article's cited primary sources, and the deepseekai.guide pricing dataset on the date shown above. Where third-party numbers are quoted, the originating source is linked inline so the reader can verify and re-check.
Data confidence
High for facts cited inline with primary-source links; medium for context narrative.
Editorial note
AI tooling moves quickly. If you spot an outdated number or a broken link, the contact link in the footer is the fastest way to flag it for the editorial team.
