Kurs
When a dashboard ships with a bug, the debugging loop is always the same. You look at the screen, find the responsible file, edit it, rerun the tests, reload the page, and check again. It's tedious, and half the evidence lives in screenshots rather than stack traces.
I started this experiment just after DeepSeek released DeepSeek V4.1 Flash. It is the smallest member of the new architecture family and accepts image input. I wanted to know whether it could inspect a broken web app, patch the code, and know when it was done.
This tutorial focuses on one project: a small Flask dashboard called Nimbus Analytics Launch Metrics with three bugs for an agent to find and fix through DeepSeek's implementation of the Responses API format. The recorded run also exposes a gap in the agent's tools.
We'll cover how to:
-
Make a first call to DeepSeek V4.1 Flash through the Responses API
-
Hand the model a reference screenshot, then feed it fresh Playwright screenshots as tool output
-
Give the agent tools to list files, read files, run pytest, and patch code across files in one call with
apply_patch -
Store and resend conversation history because the API is stateless
-
Return a structured JSON repair report
-
Calculate cost from cached input, reasoning, and output tokens
TL;DR
DeepSeek V4.1 Flash's Responses API is stateless, so the Python code stores the conversation and resends it on every turn. The same loop uses vision for the reference image and tool screenshots, thinking mode to inspect several files, and apply_patch for edits. Four details from the run changed how I would build the next version.
- One patch fixed all three bugs at once: a single apply_patch call touched the CSS, JavaScript, and Python files together, one by one, within a fourteen-turn budget.
- Context caching covered most input tokens: 137,088 of 156,724 input tokens were cached, an 87% hit rate.
- Correct diagnosis didn't guarantee full verification: the agent identified the stale Flask process correctly, but had no tool to restart it, so it couldn't confirm the visual match itself.
- Measured API cost was about $0.0103: fourteen turns in the repair loop plus the final JSON report request.
Those numbers come from a single run on one small dashboard, not a benchmark. Turn count, cache hit rate, and cost would all shift with a bigger app or a different bug set.
Working With DeepSeek in Python
What Is DeepSeek V4.1 Flash?
DeepSeek serves V4.1 Flash through the API under the model ID deepseek-flash. It accepts image input, supports thinking and non-thinking modes, has a 1M-token context window, and can return up to 384K tokens through Chat Completions and the Responses API.
Our DeepSeek V4.1 Flash overview covers the launch, architecture, and benchmarks.
How does DeepSeek V4.1 Flash work?
DeepSeek describes V4.1 Flash as a 552B-parameter MoE backbone, while Hugging Face reports 763B parameters for the published checkpoint. The gap is mostly the 196B-parameter Engram conditional memory, plus the vision encoder and projector, all components that ship in the checkpoint but sit outside the MoE backbone.
Its Causal Encoder-Decoder design reuses cached encoder states, with 8B active parameters per token during input processing and 16B during output.
What's new in DeepSeek V4.1 Flash?
V4.1 Flash is the first model in the new V4.1 architecture family, with image understanding built in natively. Visual and text embeddings are trained jointly from the start of pre-training, rather than added afterward as in the experimental V4-Flash-Vision-Exp.
The Responses API predates V4.1 Flash; DeepSeek added native support during the earlier V4 rollout. The retired model names deepseek-v4-flash and deepseek-v4-flash-vision-exp now route to V4.1 Flash.
How much does DeepSeek V4.1 Flash cost?
DeepSeek's pricing is based on peak hours, with off-peak rates set at 50% of peak. When I ran the agent, cached input cost $0.003 per million tokens off-peak and $0.006 peak, uncached input cost $0.15 off-peak and $0.30 peak, and output cost $0.60 off-peak and $1.20 peak, per DeepSeek's pricing page.
Peak hours run 01:00 to 04:00 and 06:00 to 10:00 UTC, Monday through Friday, excluding Chinese public holidays. Every other hour is off-peak, and Chinese public holidays are off-peak in full.
What We'll Build: The Launch Metrics Visual Repair Agent
Nimbus Analytics Launch Metrics is a Flask dashboard for total visitors, signups, conversion rate, revenue, and daily signups. I placed three bugs across three files and did not tell the agent what they were. The code and the broken dashboard are in this GitHub repository.

Broken dashboard next to the reference design. Image by Author.
The three bugs need different evidence. One appears in the screenshot, one affects browser behavior, and one fails pytest. The agent receives no bug list.
Before handing this to the agent, I define what "fixed" means: the pytest suite must pass, and a fresh screenshot must visually match a reference image. The model's opinion alone is not enough, so the runner checks both forms of evidence.
How the repair loop works
The loop alternates between a model request and local tool execution. V4.1 Flash returns reasoning, a message, or tool calls; Python runs the requested tools and adds the results to history. The loop stops when the model answers without another tool call or reaches the limit of fourteen turns.

Repair loop connecting model, tools, and browser. Image by Author.
How to Set Up the DeepSeek V4.1 Flash API
You'll need Python 3.10 or newer and a DeepSeek API key with credit. DeepSeek's API follows the OpenAI request format, so this project uses the openai Python package with base_url set to DeepSeek.
Create a virtual environment and install what the project needs.
python3 -m venv .venv
source .venv/bin/activate
pip install openai flask playwright pytest python-dotenv requests streamlit
playwright install chromium
I tested this with openai 3.14.1, flask 3.1.3, and playwright 1.63.0. Save the key in a .env file at the project root as DEEPSEEK_API_KEY=sk-... and load it with python-dotenv. If your key already works with the Responses API, skip the next code block; otherwise, the request checks the key and base URL.
from openai import OpenAI
import os
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(api_key=os.environ["DEEPSEEK_API_KEY"], base_url="https://api.deepseek.com")
response = client.responses.create(model="deepseek-flash", input="Say hi in five words.")
print(response.output_text)
If that prints a short greeting, the key and base URL work.
Step 1: Show the Model What "Fixed" Looks Like
The agent's first input contains a reference screenshot, a short task, and the live URL. This is the only image sent in a user message. Every later screenshot comes from a tool.
The runner sends the reference image as a base64 data URL with every request. DeepSeek recommends the Files API when an image is reused. A file_id avoids sending the same image data each time.
Getting an initial reaction before allowing any changes
In the attached image, I asked what the model would check first, but there were no tools. This let me inspect its plan before it could edit anything. The response proposed listing the project files, tracing CSS variables, and taking a screenshot; I used reasoning: {"effort": "high"}, DeepSeek's default thinking level.
Step 2: Give the Agent Tools It Can Use
The agent gets four function tools and one custom tool.
-
list_filesandread_fileinspect the project, both restricted todashboard/andtests/. -
run_testsruns pytest. -
capture_dashboard_screenshotlaunches headless Chromium through Playwright.
The custom tool is apply_patch, declared as {"type": "custom", "name": "apply_patch"} and accepted "for Codex compatibility." Any other custom tool name returns a 400 error, while built-in types like web search and computer use are silently ignored.
Function arguments arrive as JSON text and are checked before Python runs them. apply_patch arrives as a custom tool input, so the code handles it separately and checks the patch before writing files. Tool errors are returned to the model instead of stopping the loop.
Sending Playwright screenshots back as tool output
When capture_dashboard_screenshot runs, its result is not saved to disk. Python returns it as an input_image part inside function_call_output. DeepSeek then reads the screenshot as an image rather than a text description.
history.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": [{"type": "input_image", "image_url": f"data:image/png;base64,{png_b64}"}],
})
The agent can patch the CSS, take another screenshot, and check whether the numbers are readable.
Step 3: Build the Agent Loop and Manage History Yourself
History lives in a Python list because the API does not support previous_response_id or server-side conversations. Thinking mode also requires every reasoning item from earlier tool turns.
Important: If the tool output is inserted between two calls from the same turn, the next request returns a 400 error. Append every item from response.output in order, then run the tools and append their results.
The runner limits the agent to fourteen turns and the dashboard/ and tests/ directories. It provides no shell access, checks tool arguments, and uses pytest for verification.
Does DeepSeek V4.1 Flash support structured output?
Yes, via the Responses API, DeepSeek V4.1 Flash accepts a JSON Schema through text.format. The Chat Completions response_format supports JSON mode but not schemas. After the loop stops, the final request records the bugs, fixes, test results, screenshot results, and verification method.
The project also includes a Streamlit app in app_streamlit.py. The same agent runs as a generator with stream=True, so the page shows reasoning text and tool calls as they arrive. The sidebar changes the reasoning effort and image detail.
Streamlit UI streams the agent run. Video by Author.
Step 4: Run the Visual Bug-Fixing Agent
The run looked finished after one patch, but the live page disagreed.
Finding and fixing the bugs
The agent used its first two turns to look before touching anything: turn one listed the files and took a baseline screenshot, turn two read app.py, index.html, style.css, and the test file.
Turn three ran pytest, then turn four applied one patch that corrected the conversion formula, changed the metric color, and matched the JavaScript lookup to the canvas ID.
- conversion_rate = data["conversions"] / data["signups"] * 100
+ conversion_rate = data["conversions"] / data["total_visitors"] * 100
Running the tests on turn five showed all five passing. This is where the run stopped being tidy. Every new screenshot still showed a 15% conversion rate and a blank chart.
Discovering system staleness and fixing it
The agent confirmed that the files on disk contained the fixes, retried the screenshot, and probed whether the server was loading the changed Python and template files. Two temporary freshness checks also failed to appear in the live page.
By turn fourteen, the loop had reached its budget and identified the cause: run_tests checks code on disk, while the screenshot checks a running process with a stale state. Flask started with debug=False, so no reloader loaded the changed Python module, and template auto-reload was not enabled.
The CSS change appeared while the Python-derived value and template-backed chart remained stale. Pytest imported app.py from disk, so green tests did not guarantee a fresh page.
After I restarted Flask, the dashboard matched the reference image. The missing piece was a restart_server tool, not another code patch.

Restart makes patched dashboard changes visible. Image by Author.
Did the Agent Fix the Dashboard?
Yes, the agent fixed the dashboard on disk. It changed only the three faulty files, and pytest went from four failures to five passing tests. The live page showed every fix after Flask restarted.
Step 5: Measure Usage, Caching, and Cost
Because the agent resends its history, later requests repeat much of the input from earlier turns. DeepSeek checks this repeated prefix against its automatic cache. The cache operates on a best-effort basis, so these figures apply only to this run.
Across fourteen repair turns and the final JSON report request, the API reported 156,724 input tokens, including 137,088 cached tokens, an 87% hit rate. Output came to 11,497 tokens, including 9,362 reasoning tokens. The run fell during off-peak hours, so all fifteen requests cost only roughly $0.0103.

Reasoning output is the largest cost category. Image by Author.
A larger codebase, more screenshots, or fewer cache hits would change both the token count and cost.
DeepSeek V4.1 Flash API Limitations to Know
Three API limits matter before this runner grows beyond the demo.
-
Background responses are not supported, so long turns block until they finish.
-
parallel_tool_callsandmax_tool_callsare ignored; parallel tool calling stays enabled. -
Automatic truncation is not supported, so requests exceeding the context limit return a 400 error.
DeepSeek V4.1 Flash Agent Deployment Checklist
Before using this pattern in a live service, place the controls in application code rather than in model instructions.
- Enforce turn and cost limits, and alert when either is reached
- Restrict file access and check every tool argument
- tools to restart and check the service, so verification uses the current code
- Log token usage, tool calls, test results, and final status
When Should You Use apply_patch vs Plain Function Tools?
Use apply_patch when a single change must update multiple files, as it did here. Run tests after the patch, because a single bad call can damage several files.
Use read_file and write_file when each edit needs a separate check or approval. They take more turns, but a bad edit affects one file at a time.
Final Thoughts
The visual repair loop fixed all three bugs in one patch, but the run was not a clean success. Pytest passed while Flask still served the old Python and template output, so the agent could not confirm the final page until I restarted the server.
I would add a restart_server tool and a pixel comparison before testing a larger app. I would keep the file boundary and turn limit, then treat pytest and the screenshot comparison as separate checks. Passing one should never stand in for passing the other.
FAQs
Can DeepSeek V4.1 Flash read an image from a URL?
Yes. The Responses API accepts a public image URL, a base64 data URL, or a Files API file_id.
What if the agent's patch causes more test failures?
The next run_tests call shows the regression, and the loop continues until it stops or reaches its turn limit. The application should also keep a copy that it can restore.
Is DeepSeek V4 Pro being retired?
DeepSeek planned to phase out V4 Pro soon after V4.1 Flash launched, then reversed that decision after user demand. V4 Pro remains available with the same billing.
Can apply_patch be used with other models besides DeepSeek?
The format came from OpenAI's Codex tools, and DeepSeek describes its support as "for Codex compatibility." Another API will accept {"type": "custom", "name": "apply_patch"} only if it supports the same tool declaration.
Can I run DeepSeek V4.1 Flash locally?
Yes. The model weights are available on Hugging Face under the MIT license. This tutorial uses DeepSeek's hosted API and does not cover model serving or hardware requirements.
I’m a data engineer and community builder who works across data pipelines, cloud, and AI tooling while writing practical, high-impact tutorials for DataCamp and emerging developers.

