Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use this skill whenever the user wants to call DeepSeek V4-Pro / V4-Flash (or its legacy aliases deepseek-chat / deepseek-reasoner), or you see code that imports `from openai import OpenAI` with `base_url="https://api.deepseek.com"`. This skill teaches you the 10 protocol contract rules required to avoid the 16 documented bugs (reasoning_content lifecycle 400, tool-call leakage, max_tokens runaway → V8 string limit, parallel tool delta interleave, prefix-cache invalidation, /beta endpoint remap,
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 26% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 34% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 55% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 50% | 0% |
When you call DeepSeek V4-Pro or V4-Flash via the OpenAI-compatible API, you MUST follow the 10 contract rules below. Violating any one of them causes a documented production bug (the upstream issue and our reproduction probe are cited per rule).
deepseek-v4-pro defaults to thinking=enabled. Every call then burns 30-300 reasoning_tokens even on trivial prompts.
pythonclient.chat.completions.create( model="deepseek-v4-pro", messages=messages, extra_body={"thinking": {"type": "disabled"}}, # ← saves money on every non-reasoning call )
For TypeScript / openai-node, put thinking at the top level of the request, not inside extra_body (the JS SDK passes unknown top-level keys through):
tsawait openai.chat.completions.create({ model: "deepseek-v4-pro", messages, thinking: { type: "disabled" }, } as any);
reasoning_content on assistant messagesIf thinking IS enabled and you re-send a prior assistant message that has tool_calls, you must include the original reasoning_content field. Otherwise the next request returns:
HTTP 400: The reasoning_content in the thinking mode must be passed back to the API.(Reproduced in reports/probes/probe_2_reasoning_lifecycle.py 3/3 trials on V4-Pro and V4-Flash, 2026-05-09.)
pythonmsg = response.choices[0].message history.append({ "role": "assistant", "content": msg.content, "tool_calls": _serialize_tool_calls(msg.tool_calls), "reasoning_content": getattr(msg, "reasoning_content", None), # ← REQUIRED })
When a NEW user turn arrives, you MAY strip reasoning_content from prior assistant messages — DeepSeek doesn't require it across user-turn boundaries, and keeping it bloats the prefix-cache key.
max_tokens (default 4096)Without an output cap, reasoning_content can stream 8000+ chunks (probes/probe_9_reasoning_runaway.py measured 26 KB / 84 s on a self-doubt prompt) and downstream Electron clients (ChatWise, Cherry Studio) crash with RangeError: Invalid string length once their string buffer hits V8's 512 MB ceiling.
tool_calls by tc.index, not list orderDeepSeek interleaves chunks across parallel tool calls (probe_7 100% interleave on 3/3 V4-Pro and V4-Flash trials). Use a dict[int, slot]:
pythontool_call_acc: dict[int, dict] = {} for chunk in stream: for tc in (chunk.choices[0].delta.tool_calls or []): slot = tool_call_acc.setdefault(tc.index, {"id": None, "name": None, "arguments": ""}) if tc.id: slot["id"] = tc.id if tc.function and tc.function.name: slot["name"] = tc.function.name if tc.function and tc.function.arguments: slot["arguments"] += tc.function.arguments
"".join, NOT state += chunkDeepSeek streams 1-3 chars per reasoning chunk. state.text += chunk is O(n²) string allocation:
pythonbuf = [] for chunk in stream: if c := (chunk.choices[0].delta.content or ""): buf.append(c) final = "".join(buf)
DeepSeek emits ~3 chunks per response with choices == []. Check truthiness before indexing:
pythonfor chunk in stream: choices = chunk.choices or [] if not choices: if chunk.usage is not None: usage = chunk.usage continue ...
The V4-Pro / V4-Flash hard ceiling is exactly 2^20 = 1,048,576 tokens (probe_6b validated). The server enforces len(messages_tokens) + max_tokens <= 1,048,576 and returns 400 if exceeded.
DeepSeek's prefix cache buckets in 256-token blocks and gives a 50× discount on hits. To maximise:
prompt_cache_hit_tokens (DeepSeek-native) AND prompt_tokens_details.cached_tokens (OpenAI-shape) — both are returned/beta endpoint when calling V4 with tools/beta silently remaps deepseek-v4-pro → legacy deepseek-reasoner, which rejects specific tool_choice={"type":"function","function":{"name":"..."}}. Use https://api.deepseek.com for tool-using flows.
strict: true is empirically OK on V4 (despite #1069 still being open)You MAY enable function.strict=true and additionalProperties=false on V4-Pro / V4-Flash — the historic JSON-corruption bug (deepseek-ai/DeepSeek-V3#1069) was not reproducible in 32 trials on 2026-05-09. Still validate JSON post-hoc with jsonschema.
If the user's environment allows installing third-party libraries, use any of:
| form | install | command | |---|---|---| | Python lib | pip install deepseek-harness | from deepseek_harness import DeepSeekHarness | | Python CLI | pip install deepseek-harness-cli | dsh chat, dsh doctor, dsh validate | | MCP server | npx -y @deepseek-harness/mcp | wire into Claude/Cursor/Cline/ChatWise MCP config |
If they want zero deps beyond openai, copy scripts/safe_init.py (in this skill folder) into their project. It implements all 10 rules in 200 lines.
Every claim in this skill is backed by a runnable probe in reports/probes/:
bash# Reproduce the reasoning_content 400: python reports/probes/probe_2_reasoning_lifecycle.py --n 3 # Expected: 3/3 phase-B BadRequestError with the verbatim error message above. # Confirm tool-call leakage rate at 0% on official endpoint: python reports/probes/probe_3_tool_call_leakage.py --n 30 # Map cache prefix sensitivity: python reports/probes/probe_5_cache_prefix_sensitivity.py --n 8
See reports/REPORT_2026-05-09.md for the full 16-finding report.
Other measured skills in the registry, with their headline benchmark lift.