mcpField
D
deepseek-harness

deepseek-harness

github.com/henryz838978Development4752 installsUpdated 7w ago
Repository

About deepseek-harness

deepseek-harness is a skill and drop-in implementation guide for calling DeepSeek V4-Pro and V4-Flash through the OpenAI-compatible API. It documents ten request and streaming rules, with Python, CLI, MCP server, and dependency-light options.

Key features

  1. 01Ten API contract rules
  2. 02Python library and CLI options
  3. 03Drop-in safe initialization script
  4. 04Streaming and tool-call handling
  5. 05Runnable probes for reported issues

Use cases

  1. 01Configure DeepSeek V4 API requests
  2. 02Preserve reasoning data in multi-turn calls
  3. 03Process streamed parallel tool calls
  4. 04Reproduce documented API behaviors

MCP support

The skill documents an MCP server option installable with `npx -y @deepseek-harness/mcp`, for wiring into MCP configurations.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/henryz838978/deepseek-harness --skill deepseek-harness

After installing, type/deepseek-harnessin Claude Code to confirm it loaded.

Bundled filesreference/findings.mdscripts/safe_init.py

Details

From the project README

DeepSeek V4 Harness

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).

The 10 contract rules (ranked by blast radius)

C1 · Disable thinking unless the task is genuinely reasoning-heavy

deepseek-v4-pro defaults to thinking=enabled. Every call then burns 30-300 reasoning_tokens even on trivial prompts.

client.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):

await openai.chat.completions.create({
  model: "deepseek-v4-pro",
  messages,
  thinking: { type: "disabled" },
} as any);
C2 · In multi-turn loops, preserve reasoning_content on assistant messages

If 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.)

msg = 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.

C3 · Always set 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.

C4 · Streaming: aggregate parallel tool_calls by tc.index, not list order

DeepSeek interleaves chunks across parallel tool calls (probe_7 100% interleave on 3/3 V4-Pro and V4-Flash trials). Use a dict[int, slot]:

tool_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
C5 · Streaming: list buffer + "".join, NOT state += chunk

DeepSeek streams 1-3 chars per reasoning chunk. state.text += chunk is O(n²) string allocation:

buf = []
for chunk in stream:
    if c := (chunk.choices[0].delta.content or ""):
        buf.append(c)
final = "".join(buf)
C6 · Tolerate empty stream chunks

DeepSeek emits ~3 chunks per response with choices == []. Check truthiness before indexing:

for chunk in stream:
    choices = chunk.choices or []
    if not choices:
        if chunk.usage is not None: usage = chunk.usage
        continue
    ...
C7 · Cap context length under 1,048,576 tokens

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.

C8 · Cache awareness — do NOT inject volatile content into the system prompt

DeepSeek's prefix cache buckets in 256-token blocks and gives a 50× discount on hits. To maximise:

  • Do NOT prune or summarise history aggressively (every prune = cache miss)
  • Do NOT inject "current date: 2026-05-09" or similar into the cached prefix
  • Do read both prompt_cache_hit_tokens (DeepSeek-native) AND prompt_tokens_details.cached_tokens (OpenAI-shape) — both are returned
C9 · Avoid /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.

C10 · 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.


Drop-in implementation

If the user's environment allows installing third-party libraries, use any of:

forminstallcommand
Python libpip install deepseek-harnessfrom deepseek_harness import DeepSeekHarness
Python CLIpip install deepseek-harness-clidsh chat, dsh doctor, dsh validate
MCP servernpx -y @deepseek-harness/mcpwire 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.

Reproduction commands

Every claim in this skill is backed by a runnable probe in reports/probes/:

# 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.

Comments

Markdown supported