n4nAI

How to migrate prompts from OpenAI to DeepSeek

Practical steps to migrate prompts from OpenAI to DeepSeek, covering chat format, tool calls, client changes, and verification to avoid silent regressions.

n4n Team3 min read708 words

Audio narration

Coming soon — every post will get a voice note here.

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-chat vs 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 functions field: DeepSeek ignores it; upgrade to tools.
  • Temperature on reasoner: silently ignored, not an error.
  • Different stop tokens: if you relied on OpenAI’s default stop, set explicit stop lists.

Final checklist

  1. Extracted all prompt templates to JSON.
  2. Mapped roles; handled R1 system prompt.
  3. Converted functions to tools.
  4. Set explicit max_tokens and stop.
  5. Switched base_url and model.
  6. Ran differential test with ≥20 cases.
  7. Flag-flipped with fallback.

Follow these steps and migrating prompts OpenAI to DeepSeek becomes a half-day task instead of a multi-week scramble.

Tagsopenaideepseekmigrationprompts

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All migrating between llm providers posts →