Migrating prompts OpenAI to DeepSeek is mostly mechanical, but sharp edges in chat formatting, system message handling, and tool calling will bite if you skip them. This guide gives an end-to-end procedure to port existing OpenAI prompt templates and runtime calls to DeepSeek’s API without rewriting your application logic.
Step 1: Audit your existing OpenAI prompt structures
Before changing any code, extract every prompt construction site. In a typical Python service this means locating where you build the messages array and where you declare functions or tools.
# audit_example.py
import inspect, re
def find_openai_calls(path):
src = open(path).read()
return re.findall(r"client\.chat\.completions\.create\(([^)]*)\)", src)
print(find_openai_calls("app/services/llm.py"))
Capture the following for each call:
- System prompt text and any templating variables.
- Few-shot user/assistant pairs.
- Tool/function schemas.
- Sampling params (
temperature,top_p,max_tokens,stop).
Store these in a version-controlled JSON file. You will diff against this after migration.
Step 2: Map message roles and chat format
DeepSeek’s deepseek-chat model is OpenAI-compatible for role names: system, user, assistant. The migration of message arrays is usually a straight rename of the model field.
{
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "You are a concise SQL expert."},
{"role": "user", "content": "List top 5 customers by revenue"}
]
}
The exception is deepseek-reasoner (the R1 model). It does not accept a system role. If your prompt relies on a system message, prepend it to the first user turn:
def to_reasoner_messages(openai_messages):
out = []
sys = ""
for m in openai_messages:
if m["role"] == "system":
sys += m["content"] + "\n"
else:
out.append(m)
if sys:
out[0]["content"] = sys + out[0]["content"]
return out
Do not silently drop the system prompt; that causes the most common quality regression we see when migrating prompts OpenAI to DeepSeek.
Step 3: Convert function and tool definitions
OpenAI’s tools format is a JSON schema list. DeepSeek-chat supports the same shape. Copy the schema verbatim, but verify that strict mode is not required—DeepSeek does not enforce strict function calling the way OpenAI’s latest models can.
{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
}
]
}
If you used OpenAI’s legacy functions field, upgrade it to tools before sending to DeepSeek. The legacy format is ignored by DeepSeek’s endpoint.
Step 4: Adjust sampling parameters and stop sequences
DeepSeek-chat honors temperature, top_p, max_tokens, and stop. DeepSeek-reasoner ignores temperature (it runs at 0.0) and does not support logprobs. If your OpenAI code sets temperature=0.7 for creative tasks, expect different outputs from R1.
# openai_compatible_call.py
from openai import OpenAI
client = OpenAI(base_url="https://api.deepseek.com", api_key="<KEY>")
resp = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
temperature=0.3,
max_tokens=512,
stop=["\n\n"]
)
When migrating prompts OpenAI to DeepSeek, keep max_tokens explicit. DeepSeek’s default completion length is smaller than some OpenAI defaults, and silent truncation wastes debugging time.
Step 5: Update client code to target DeepSeek
The minimal change is swapping base_url and model. If you use the OpenAI Python SDK, this is three lines.
from openai import OpenAI
client = OpenAI(
base_url="https://api.deepseek.com/v1",
api_key=os.environ["DEEPSEEK_API_KEY"]
)
model = "deepseek-chat"
If you want to avoid maintaining separate clients, route through an OpenAI-compatible gateway such as n4n.ai, which exposes one endpoint covering 240+ models including DeepSeek and provides automatic fallback when a provider is rate-limited. You then only change the model string and keep the same base_url.
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_KEY"]
)
# no other code change; just set model="deepseek/deepseek-chat"
Step 6: Handle response shape differences
Both APIs return choices[0].message.content and usage. DeepSeek includes prompt_tokens_details for some models but you should not depend on it. If you parse finish_reason, note that DeepSeek uses stop, length, and tool_calls identically to OpenAI.
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
For tool calls, DeepSeek returns message.tool_calls with function.name and function.arguments as a JSON string—same as OpenAI. Parse it with json.loads.
Step 7: Verify with differential testing
Migrating prompts OpenAI to DeepSeek without a golden set is guesswork. Build a small evaluation harness that runs both providers on fixed inputs and compares outputs for key invariants.
# test_migration.py
import os, json
from openai import OpenAI
def complete(client, model, messages):
return client.chat.completions.create(
model=model, messages=messages, temperature=0
).choices[0].message.content
def test_sql_prompt():
msgs = [{"role":"system","content":"Output only SQL"},
{"role":"user","content":"top customers"}]
oai = complete(OpenAI(api_key=os.environ["OAI"]), "gpt-4o-mini", msgs)
ds = complete(OpenAI(base_url="https://api.deepseek.com/v1",
api_key=os.environ["DS"]), "deepseek-chat", msgs)
assert "SELECT" in oai and "SELECT" in ds
with open("diff.log","a") as f:
f.write(json.dumps({"oai":oai,"ds":ds})+"\n")
Run pytest test_migration.py. Success means no exceptions, assertions hold, and the diff log shows semantically equivalent answers for at least 20 curated cases.
Step 8: Cut over and monitor per-token cost
Flip the default model in your config. Keep the OpenAI path behind a flag for one week. Because DeepSeek’s tokenization differs, prompt token counts will shift; meter them.
If you used n4n.ai, per-token usage metering is already broken down by provider, so you can spot DeepSeek’s smaller prompt token counts directly in the dashboard.
Monitor:
- Latency p95 for
deepseek-chatvs OpenAI. - Tool-call success rate.
- Rate-limit errors (DeepSeek free tier is strict; production needs paid quota).
Common pitfalls
- System message dropped for R1: always fold it into user text.
- Legacy
functionsfield: DeepSeek ignores it; upgrade totools. - Temperature on reasoner: silently ignored, not an error.
- Different stop tokens: if you relied on OpenAI’s default stop, set explicit
stoplists.
Final checklist
- Extracted all prompt templates to JSON.
- Mapped roles; handled R1 system prompt.
- Converted
functionstotools. - Set explicit
max_tokensandstop. - Switched
base_urlandmodel. - Ran differential test with ≥20 cases.
- Flag-flipped with fallback.
Follow these steps and migrating prompts OpenAI to DeepSeek becomes a half-day task instead of a multi-week scramble.