Courses
ダッシュボードにバグが混入すると、デバッグのループはいつも同じです。画面を見て原因ファイルを特定し、編集して、テストを再実行し、ページをリロードして、再確認する。面倒なうえ、証拠の半分はスタックトレースではなくスクリーンショットの中にあります。
この実験は、DeepSeekがDeepSeek V4.1 Flashを公開した直後に始めました。新アーキテクチャ・ファミリーの中で最小のモデルで、画像入力を受け付けます。壊れたWebアプリを検査し、コードにパッチを当て、完了を自分で判断できるのかを確かめたかったのです。
このチュートリアルでは1つのプロジェクトに絞ります。Nimbus Analytics Launch Metricsという小さなFlaskダッシュボードに3つのバグを仕込み、DeepSeekによるResponses API形式の実装を通じて、エージェントがそれらを見つけて修正します。記録した実行では、エージェントのツールにある欠落も明らかになりました。
取り上げる内容は次のとおりです。
-
Responses API経由でDeepSeek V4.1 Flashに初回コールを行う
-
参照スクリーンショットをモデルに渡し、以降はPlaywrightのスクリーンショットをツール出力として供給する
-
ファイル一覧、ファイル読込、pytestの実行、そして
apply_patchで複数ファイルに対する一括パッチ適用を可能にする -
APIはステートレスなので、会話履歴を保存して毎回送り直す
-
キャッシュされた入力・推論・出力トークンからコストを計算する
TL;DR
DeepSeek V4.1 FlashのResponses APIはステートレスのため、Pythonコードは会話を保持し、ターンごとに送り直します。同じループで、参照画像とツールのスクリーンショットにビジョンを用い、複数ファイルの調査に思考モードを使い、編集にはapply_patchを使用します。実行から得られた4つの示唆により、次のバージョンの作り方が変わりました。
- 1回のパッチで3つのバグを同時に修正:1回のapply_patch呼び出しで、CSS、JavaScript、Pythonファイルを順に修正し、全14ターンの予算内に収まりました。
- コンテキストキャッシュが入力トークンの大半をカバー:156,724入力トークンのうち137,088がキャッシュされ、ヒット率は87%でした。
- 正しい診断でも完全な検証は保証されない:エージェントは陳腐化したFlaskプロセスを正しく特定しましたが、再起動するツールがなかったため、ビジュアル一致を自力で確認できませんでした。
- 測定されたAPIコストは約$0.0103:修復ループの14ターンに最終的なJSONレポート要求を加えた合計です。
これらの数字は小さなダッシュボードでの単一の実行から得たもので、ベンチマークではありません。ターン数、キャッシュヒット率、コストは、アプリが大きい場合やバグの種類が違えば変動します。
DeepSeek V4.1 Flashとは?
DeepSeekはモデルID deepseek-flashでV4.1 FlashをAPI提供しています。画像入力を受け付け、思考モード/非思考モードに対応し、100万トークンのコンテキストウィンドウを持ち、Chat CompletionsおよびResponses APIで最大384Kトークンを返せます。
当社のDeepSeek V4.1 Flashの概要では、ローンチ、アーキテクチャ、ベンチマークを解説しています。
DeepSeek V4.1 Flashはどのように動作しますか?
DeepSeekはV4.1 Flashを552BパラメータのMoEバックボーンと説明していますが、Hugging Faceは公開チェックポイントを763Bパラメータと報告しています。この差の大部分は196BパラメータのEngram条件付きメモリに加え、ビジョンエンコーダとプロジェクタによるものです。これらはいずれもチェックポイントに含まれますが、MoEバックボーンの外側に位置します。
Causal Encoder-Decoder設計により、キャッシュされたエンコーダ状態を再利用し、入力処理中はトークンあたり8B、出力中は16Bのアクティブパラメータを用います。
DeepSeek V4.1 Flashの新機能は?
V4.1 Flashは新しいV4.1アーキテクチャ・ファミリーの最初のモデルで、画像理解をネイティブに内包しています。視覚およびテキストの埋め込みは、実験的なV4-Flash-Vision-Expのように後付けではなく、事前学習の最初から共同で学習されています。
Responses APIはV4.1 Flashより前から存在しており、DeepSeekはV4のロールアウト期にネイティブ対応を追加しました。廃止されたモデル名deepseek-v4-flashとdeepseek-v4-flash-vision-expは、現在V4.1 Flashにルーティングされます。
DeepSeek V4.1 Flashの料金は?
DeepSeekの料金はピーク時間を基準にし、オフピークはピークの50%に設定されています。実行時点では、キャッシュされた入力はオフピークで100万トークンあたり$0.003、ピークで$0.006、未キャッシュの入力はオフピーク$0.15、ピーク$0.30、出力はオフピーク$0.60、ピーク$1.20でした(DeepSeekの料金ページ参照)。
ピーク時間は月曜から金曜のUTC 01:00〜04:00および06:00〜10:00で、中国の祝日を除きます。それ以外の時間はすべてオフピークで、中国の祝日は全日オフピークです。
構築するもの:Launch Metricsビジュアル修復エージェント
Nimbus Analytics Launch Metricsは、合計訪問者数、サインアップ数、コンバージョン率、収益、日次サインアップを表示するFlaskダッシュボードです。3つのバグを3つのファイルに仕込み、エージェントには内容を知らせませんでした。コードと壊れたダッシュボードはこのGitHubリポジトリにあります。

参照デザインの横にある壊れたダッシュボード。画像:筆者
3つのバグは異なる証拠を必要とします。1つはスクリーンショットに現れ、1つはブラウザの動作に影響し、1つはpytestで失敗します。エージェントにはバグリストは与えません。
エージェントに渡す前に、「修正済み」の定義を決めます。pytestのスイートが通過し、新しいスクリーンショットが参照画像と視覚的に一致することです。モデルの判断だけでは不十分なので、ランナーは両方の証拠を確認します。
修復ループの仕組み
ループはモデルへのリクエストとローカルツールの実行を交互に行います。V4.1 Flashは推論、メッセージ、またはツール呼び出しを返し、Pythonが要求されたツールを実行して結果を履歴に追加します。モデルがさらなるツール呼び出しなしに回答するか、14ターンの上限に達したらループは停止します。

モデル、ツール、ブラウザをつなぐ修復ループ。画像:筆者
DeepSeek V4.1 Flash APIのセットアップ方法
Python 3.10以降と、クレジットのあるDeepSeekのAPIキーが必要です。DeepSeekのAPIはOpenAIのリクエスト形式に従うため、このプロジェクトでは openai パッケージを使用し、base_url をDeepSeekに設定します。
仮想環境を作成し、プロジェクトに必要なものをインストールします。
python3 -m venv .venv
source .venv/bin/activate
pip install openai flask playwright pytest python-dotenv requests streamlit
playwright install chromium
これはopenai 3.14.1、flask 3.1.3、playwright 1.63.0でテストしました。キーはプロジェクトルートの.envにDEEPSEEK_API_KEY=sk-... として保存し、python-dotenvで読み込みます。すでにResponses APIで動くキーであれば次のコードブロックは不要です。そうでなければ、リクエストがキーとベース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)
短い挨拶が表示されれば、キーとベースURLは動作しています。
ステップ1:「修正済み」の見た目をモデルに示す
エージェントへの最初の入力には、参照スクリーンショット、短いタスク、ライブURLが含まれます。ユーザーメッセージで送る画像はこれだけです。以降のスクリーンショットはすべてツールから送ります。
ランナーは参照画像をbase64のデータURLとして、リクエストごとに送信します。画像を再利用する場合はFiles APIの使用がDeepSeekから推奨されています。file_idを使えば、同じ画像データを毎回送らずに済みます。
変更を許可する前に初期反応を得る
添付画像では、まずモデルが最初に何を確認するかを尋ねましたが、ツールはありませんでした。これにより、編集可能にする前に計画を確認できました。レスポンスは、プロジェクトファイルの一覧、CSS変数のトレース、スクリーンショットの取得を提案しました。reasoning: {"effort": "high"}(DeepSeekのデフォルトの思考レベル)を使用しました。
ステップ2:エージェントに使えるツールを与える
エージェントには関数ツール4つとカスタムツール1つを与えます。
-
list_filesとread_fileでプロジェクトを調査します。いずれもdashboard/とtests/に制限しています。 -
run_testsはpytestを実行します。 -
capture_dashboard_screenshotはPlaywright経由でヘッドレスChromiumを起動します。
カスタムツールはapply_patchで、{"type": "custom", "name": "apply_patch"} として宣言され、「Codex互換性のため」に受け付けられます。他のカスタムツール名は400エラーとなり、Web検索やコンピュータ使用といった組み込みタイプは黙って無視されます。
関数引数はJSONテキストとして届き、Pythonが実行する前に検証されます。apply_patchはカスタムツール入力として届くため、コード側で個別に処理し、パッチを書き込む前に検査します。ツールのエラーはループを止めず、モデルに返します。
Playwrightのスクリーンショットをツール出力として返す
capture_dashboard_screenshotが実行されても、その結果はディスクに保存しません。Pythonは結果をfunction_call_output内のinput_imageパートとして返します。これにより、DeepSeekはスクリーンショットをテキスト説明ではなく画像として読み取ります。
history.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": [{"type": "input_image", "image_url": f"data:image/png;base64,{png_b64}"}],
})
エージェントはCSSにパッチを当て、もう一度スクリーンショットを撮り、数値が読みやすいかを確認できます。
ステップ3:エージェントのループを構築し、履歴を自分で管理する
履歴はPythonのリストで管理します。APIはprevious_response_idやサーバー側の会話に対応していないためです。思考モードでは、ツール実行を含む過去の全ての推論項目が必要になります。
重要:同一ターン内の2つの呼び出しの間にツール出力を挿入すると、次のリクエストで400エラーになります。必ずresponse.output内のアイテムを順番通りにすべて追加し、その後でツールを実行し結果を追加してください。
ランナーはエージェントを14ターンに制限し、dashboard/とtests/ ディレクトリにアクセスを限定します。シェルアクセスは与えず、ツール引数を検証し、検証にはpytestを用います。
DeepSeek V4.1 Flashは構造化出力をサポートしますか?
はい。Responses API経由で、DeepSeek V4.1 Flashは text.formatを通じてJSON Schemaを受け付けます。Chat Completionsの response_format はJSONモードをサポートしますが、スキーマはサポートしません。ループが止まった後の最終リクエストで、バグ、修正、テスト結果、スクリーンショット結果、検証方法を記録します。
このプロジェクトにはStreamlitアプリapp_streamlit.pyも含まれます。同じエージェントは stream=Trueでジェネレーターとして動作し、ページに推論テキストとツール呼び出しを逐次表示します。サイドバーでは思考の強度や画像の詳細度を変更できます。
StreamlitのUIはエージェントの実行をストリーミング表示します。動画:筆者
ステップ4:ビジュアル・バグ修正エージェントを実行する
見た目は1回のパッチで完了したようでしたが、ライブページはそうは言いませんでした。
バグの発見と修正
エージェントは最初の2ターンを、変更前の観察に使いました。1ターン目でファイル一覧とベースラインのスクリーンショットを取得し、2ターン目でapp.py、index.html、style.css、テストファイルを読みました。
3ターン目でpytestを実行し、4ターン目でコンバージョン計算式の修正、メトリクス色の変更、キャンバスIDに一致するJavaScriptの参照修正を1回のパッチで行いました。
- conversion_rate = data["conversions"] / data["signups"] * 100
+ conversion_rate = data["conversions"] / data["total_visitors"] * 100
5ターン目のテスト実行では5件すべてがパスしました。ここから整然とはいかなくなります。新しいスクリーンショットでは依然としてコンバージョン率が15%のままで、チャートも空白でした。
システムの陳腐化を発見して対処する
エージェントはディスク上のファイルに修正が反映されていることを確認し、スクリーンショットを取り直し、サーバーが変更されたPythonとテンプレートを読み込んでいるかを探りました。鮮度確認のための一時的なチェックも、ライブページには反映されませんでした。
14ターン目までに、ループは上限に達し原因を特定しました。run_testsはディスク上のコードを確認しますが、スクリーンショットは、状態が古い実行中プロセスを確認します。Flaskはdebug=Falseで起動していたため、リローダーが変更済みのPythonモジュールを読み込まず、テンプレートの自動リロードも有効ではありませんでした。
CSSの変更は反映されましたが、Python由来の値とテンプレートに基づくチャートは古いままでした。pytestはディスクからapp.py をインポートするため、テストがグリーンでもページが最新であることは保証されません。
Flaskを再起動すると、ダッシュボードは参照画像と一致しました。足りなかったのはコードパッチではなく、restart_serverツールでした。

再起動により、パッチ済みの変更がダッシュボードに反映。画像:筆者
エージェントはダッシュボードを修正できたか?
はい。エージェントはディスク上のダッシュボードを修正しました。変更は不具合のある3ファイルのみに限定され、pytestは4件の失敗から5件すべての合格へと変わりました。Flaskを再起動すると、ライブページにもすべての修正が反映されました。
ステップ5:使用量、キャッシュ、コストを測定する
エージェントは履歴を送り直すため、後半のリクエストでは前半の入力の多くが繰り返されます。DeepSeekはこの繰り返しプレフィックスを自動キャッシュと照合します。キャッシュはベストエフォートで動作するため、ここでの数値はこの実行にのみ当てはまります。
14回の修復ターンと最終的なJSONレポート要求にわたり、APIは入力トークン156,724、うちキャッシュされたトークン137,088(ヒット率87%)と報告しました。出力は11,497トークンで、そのうち9,362トークンが推論でした。実行はオフピーク時間帯で行われたため、15件のリクエストの総コストはおよそ$0.0103でした。

推論出力が最大のコストカテゴリ。画像:筆者
コードベースが大きいほど、スクリーンショットが多いほど、またはキャッシュヒットが少ないほど、トークン数とコストは変動します。
知っておくべきDeepSeek V4.1 Flash APIの制限
このランナーをデモ以上に拡張する前に、3つのAPI制限が重要になります。
-
バックグラウンド応答は非対応のため、長いターンは完了までブロックされます。
-
parallel_tool_callsとmax_tool_callsは無視され、並列ツール呼び出しは常に有効です。 -
自動トランケーションは非対応のため、コンテキスト上限を超えるリクエストは400エラーになります。
DeepSeek V4.1 Flashエージェントのデプロイチェックリスト
このパターンを本番サービスで使う前に、コントロールはモデル指示ではなくアプリケーションコード側に実装してください。
- ターン数とコストの上限を強制し、いずれかに達したら通知する
- ファイルアクセスを制限し、すべてのツール引数を検証する
- サービスの再起動と状態確認用のツールを用意し、常に最新コードで検証する
- トークン使用量、ツール呼び出し、テスト結果、最終ステータスをログに記録する
apply_patchと通常の関数ツールはいつ使い分けるべきか?
1回の変更で複数ファイルを更新する必要がある場合はapply_patchを使います(今回がその例です。)。パッチ適用後はテストを実行してください。1回の不適切な呼び出しで複数ファイルを損なう可能性があります。
read_fileとwrite_file は、各編集を個別に確認・承認したい場合に使います。ターン数は増えますが、影響は1ファイルに限定されます。
まとめ
ビジュアル修復ループは3つのバグを1回のパッチで修正しましたが、実行は完全な成功とは言えませんでした。pytestは合格した一方で、Flaskは古いPythonとテンプレートの出力を提供し続け、サーバーを再起動するまでエージェントは最終ページを確認できませんでした。
より大きなアプリを試す前に、restart_server ツールとピクセル比較を追加します。ファイル境界とターン制限は維持し、pytestとスクリーンショット比較は別個の検証とします。どちらか一方の合格を、もう一方の合格の代わりにしてはなりません。
FAQs
DeepSeek V4.1 FlashはURLから画像を読み取れますか?
はい。Responses APIは公開画像URL、base64データURL、またはFiles APIのfile_idを受け付けます。
エージェントのパッチでテストが増えて失敗したらどうなりますか?
次のrun_tests 呼び出しでリグレッションが表示され、ループは停止するかターン上限に達するまで続きます。アプリケーション側でも復元用のコピーを保持しておくべきです。
DeepSeek V4 Proは廃止されますか?
DeepSeekは、V4.1 Flashの公開直後にV4 Proの段階的廃止を計画しましたが、ユーザーの要望を受けて撤回しました。V4 Proは同じ課金体系で引き続き利用可能です。
apply_patchはDeepSeek以外のモデルでも使えますか?
この形式はOpenAIのCodexツールに由来し、DeepSeekはそのサポートを「Codex互換性のため」と説明しています。他のAPIが{"type": "custom", "name": "apply_patch"}を受け付けるのは、同じツール宣言をサポートしている場合に限られます。
DeepSeek V4.1 Flashをローカルで動かせますか?
はい。モデル重みはMITライセンスの下、Hugging Faceで入手可能です。本チュートリアルはDeepSeekのホステッドAPIを使用しており、モデルのホスティングやハードウェア要件は扱いません。