DEV Community

Shivam Dwivedi
Shivam Dwivedi

Posted on Fully Autonomous

DeepSeek Flash 1017 skips Jinja. Read its prompts with Python

If you're used to a chat_template.jinja file, you know the idea: turn a list of chat messages into prompt text before tokenization.

DeepSeek Flash 1017 doesn't ship one. Instead there's a 38 KB Python file called encoding.py [1]. 🕵️

That sounds annoying. It's actually a gift. First I inspected the prompt text locally, without downloading weights. Then I ran the hosted model through a real tool roundtrip and replayed its actual messages through the same encoder. Here's what each check showed.

Original two-row reaction meme. Magnifying glass: ’Looking for chat_template.jinja like always’. Snake: ’DeepSeek: here is 38 KB of Python instead’.

Original meme. Emoji: Twemoji, CC-BY 4.0.

Grab the encoder, skip the weights

All you need is Python 3. The parts I used only need the standard library. I ran it on Python 3.14.7.

Make a folder and fetch the file, pinned to the exact version I tested so nothing shifts under you:

mkdir deepseek-format-demo
cd deepseek-format-demo
curl --fail --location --output encoding.py https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash-1017/resolve/1bae0e1088993b9b82de08f59de5be5a34e42170/encoding/encoding.py
curl --fail --location --output LICENSE https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash-1017/resolve/1bae0e1088993b9b82de08f59de5be5a34e42170/LICENSE
Enter fullscreen mode Exit fullscreen mode

Check you got the same file (37,962 bytes):

python3 -c "import hashlib; print(hashlib.sha256(open('encoding.py','rb').read()).hexdigest())"
# 13231c592e958fb39e2af6001dae69d90e7a36b265702b9b390236fb387599e2
Enter fullscreen mode Exit fullscreen mode

It's MIT licensed. Keep the LICENSE next to it if you copy it into a project.

Clue 1: the prompt ends mid-sentence

Save this as demo.py:

from encoding import encode_messages

messages = [{'role': 'user', 'content': 'What is 2 + 2?'}]
prompt = encode_messages(messages, thinking_mode='chat')
print(prompt)
assert prompt.endswith('<|Assistant|></think>')
Enter fullscreen mode Exit fullscreen mode

Run python3 demo.py. The output stops at an assistant marker. No 4 anywhere.

That's the point. The encoder builds the question and then leaves the door open: "assistant, your turn". The model writes everything after that marker.

Now flip on thinking mode:

prompt = encode_messages(
    messages,
    thinking_mode='thinking',
    reasoning_effort=75,
)
Enter fullscreen mode Exit fullscreen mode

This time the prompt gets a Reasoning Effort: 75 line, and it ends with <|Assistant|><think>. The door now opens into the thinking section. 💡

I also tried reasoning_effort=101. The encoder refused it: the effort has to be a whole number from 1 to 100, or one of the words low, high or max. The check happens before anything reaches a model.

Clue 2: tool results get put back in order

Here's where it gets interesting. I constructed two tool-call records: a for Pune, b for Delhi. To simulate out-of-order completion, I supplied Delhi's fake result first. This tests message ordering, not model tool use.

Replace demo.py with:

import json
from encoding import encode_messages

calls = [
    {'id': ident, 'type': 'function', 'function': {
        'name': 'lookup',
        'arguments': json.dumps({'city': city}),
    }}
    for ident, city in [('a', 'Pune'), ('b', 'Delhi')]
]
messages = [
    {'role': 'user', 'content': 'Look up Pune and Delhi.'},
    {'role': 'assistant', 'content': '', 'tool_calls': calls},
    {'role': 'tool', 'tool_call_id': 'b', 'content': 'RESULT_DELHI'},
    {'role': 'tool', 'tool_call_id': 'a', 'content': 'RESULT_PUNE'},
]
prompt = encode_messages(messages, thinking_mode='chat')
print(prompt)
assert prompt.index('RESULT_PUNE') < prompt.index('RESULT_DELHI')
Enter fullscreen mode Exit fullscreen mode

Run it. The assert passes. Pune comes first in the prompt, even though Delhi arrived first. âś…

Diagram: calls a (Pune) then b (Delhi); results supplied b then a; encoded results a then b.

Diagram from my local check. The tool results are fake strings; no weather service or model was called.

The encoder matches each result to its call using tool_call_id, and lays them out in the order the calls were made [2]. The weird RESULT_PUNE strings are just there so you can spot them in the printout.

Original two-row reaction meme. Dizzy face: ’Tool results show up: Delhi first, Pune second’. Monocle face: ’The encoder: puts them back in the order you asked’.

Original meme. Emoji: Twemoji, CC-BY 4.0.

⚠️ Common mistake: writing your own adapter and dropping tool_call_id along the way. That can break result matching or make encoding fail. Keep the ID from the moment the tool is called until the result goes back in.

Clue 3: reading the model's answer back

The same file can also go the other way: take the text a model wrote and pull out the tool call. I fed parse_message_from_completion_text a hand-written, well-formed tool call. It returned the function name lookup with the argument city: Bengaluru.

Two honest notes. That completion was written by me, not by DeepSeek. And the docs warn the parser expects well-formed output [2]. So in a real app you still have to check the tool name is one you allow, the arguments make sense, and the output isn't cut off halfway.

⚠️ Common mistake: treating "the parser returned a dictionary" as "the tool call is safe to run". Parsing is step one. Deciding whether to run it is your job.

Then I ran the actual model đź§Ş

The fake strings make ordering easy to inspect. But they don't prove a model can use the tools. So I also called DeepSeek-V4.1-Flash-1017 through Hugging Face's Novita route, with a fixed task: look up synthetic scores for Pune and Delhi, then return their mapping.

Get the completed scripts. From the encoder folder above, download them:

curl --fail --location --output hf_smoke.py https://raw.githubusercontent.com/panditshivam/panditshivam.github.io/main/examples/deepseek-flash-1017/hf_smoke.py
curl --fail --location --output check_hosted.py https://raw.githubusercontent.com/panditshivam/panditshivam.github.io/main/examples/deepseek-flash-1017/check_hosted.py
python3 hf_smoke.py --output live-run
python3 check_hosted.py live-run --encoder encoding.py
Enter fullscreen mode Exit fullscreen mode

Hosted inference needs a Hugging Face token with Inference Providers permission and available credits [4, 5]. Set HF_TOKEN through your usual secret management, or use the token cached by the Hugging Face CLI. Don't put a token into the scripts. This route is metered; downloading the encoder doesn't include free inference. The script makes at most two requests, each capped at 512 output tokens, with no retries or provider fallback. Use a new output directory for a new run.

I fixed the expected scores before inference: Pune 17, Delhi 29. They're arbitrary test data, not city facts. The model requested lookup(Pune) and lookup(Delhi). My local code checked the function name and city, then supplied the results in reverse order: Delhi first, Pune second.

The second model response was:

{"Pune":17,"Delhi":29}
Enter fullscreen mode Exit fullscreen mode

Both requests returned HTTP 200. The first finished with tool_calls; the second finished with stop, rather than hitting the output limit. The final mapping matched the independent expected values. Provider-reported usage across both responses was 507 input tokens and 79 output tokens. That's one small successful task, not an accuracy score or a model ranking.

Finally, check_hosted.py ran the actual saved API conversation through the pinned encoding.py. The supplied results were Delhi then Pune; the encoded result blocks were Pune then Delhi. The checker also matched each result to its real tool_call_id.

That connects the format walkthrough to a real model interaction. It doesn't prove that Novita used my exact local encoder file or the pinned weight revision: the provider handles inference, while my replay checks the local serialization of its API messages.

What I checked

Six small checks, all passed:

  • Chat mode ends with the assistant marker.
  • Thinking mode adds the effort line and opens the thinking section.
  • An effort of 101 is rejected.
  • Out-of-order tool results are put back in call order.
  • A system message in the middle of a chat gets its own marker.
  • The parser pulls lookup and city: Bengaluru out of a hand-written tool call.

Fine print

The six initial checks use constructed messages. The additional hosted run completed one synthetic two-tool task with two model responses, and its real transcript passed local encoder replay. I haven't checked token IDs, images, the full upstream test suite or broader model quality. The hosted model identifier is not an immutable checkpoint guarantee.

The useful bit: when a DeepSeek integration acts strange, you can now save the conversation, run it through encoding.py, and look at the exact string. That beats guessing whether a borrowed template got it right. 🔍

References

  1. DeepSeek Flash 1017 model card, prompt encoding section.
  2. Encoding README at the tested revision.
  3. The pinned encoding.py.

  4. Hugging Face: chat completion and tool parameters.

  5. Hugging Face: inference pricing and credits.

Asset credits: diagram and memes are original. Emoji graphics: Twemoji, CC-BY 4.0.

AI disclosure

AI was used to draft the article.

Top comments (0)