修理メモを DeepSeek のモデルに送り、点検した内容、推奨した作業、顧客が承認したか、作業が完了したかを JSON オブジェクトで返してもらうサービスがあるとします。現在は deepseek-chat、deepseek-flash、または OpenRouter のルートを呼び出していて、これを SpicyAPI(spicyapi.ai)経由で DeepSeek V4.1 Flash に切り替えたい、という場面です。
必要な変更は4つです。モデルIDに deepseek/v4.1-flash/chat を使い、このモデルが受け付けないフィールドをすべて削除し、reasoning_effort を選び、max_tokens に十分な余裕を持たせます。推論は同じ予算から支払われるためです。 そのうえで、本番のトラフィックを流す前に小さなテストを1件実行してください。リクエストは V4.1 Flash のモデルページで試せます。
リクエストで何を変えるか
ベースURL、キー、モデルIDは簡単な部分です(移行ガイド)。つまずくのはリクエスト本文です。
| 現在のクライアント | SpicyAPI |
|---|---|
ベースURL https://api.deepseek.com と DeepSeek のキー | https://api.spicyapi.ai/v1 と sk-spicy- で始まるキー |
モデル deepseek-flash、または古い deepseek-chat、deepseek-reasoner、deepseek-v4-flash、deepseek-v4-pro | deepseek/v4.1-flash/chat |
thinking: {"type": "disabled"}(deepseek-chat がかつて示していた動作) | reasoning_effort: "none" で推論の工程を省略します |
| thinking オン(デフォルト) | reasoning_effort を省略すると high になります。大量処理には low を指定します |
reasoning_effort が max、medium、minimal、xhigh | 受け付けるのは none、low、high のみで、それ以外は 400 invalid_request になります |
temperature、top_p、presence_penalty、frequency_penalty | 削除します。400 unsupported_parameter になります |
parallel_tool_calls | 削除します。このモデルでは公開されていません |
stop | そのまま使えます。最大4つのシーケンスで、一致したテキストは応答に含まれません |
image_url またはファイルのパート | 画像用の image_url パートは残し、PDF/ファイルのパートは削除して先にテキストを抽出します |
OpenRouter の models、route、provider、usage、transforms、plugins、および reasoning オブジェクト | 最初の6つは削除し、reasoning はトップレベルの reasoning_effort に置き換えます |
このホスト型モデルにはサンプリング制御がなく、reasoning_effort: "none" でも同じです。再現性を temperature: 0 に頼らず、タスクの結果そのものを検証してください。
変わらない点は次のとおりです。messages(テキストまたは画像、最大256件)、tools(定義は最大128件)と tool_choice(auto、none、required、または名前を指定した1つの関数)、response_format({"type": "json_object"} を指定)、stop、max_tokens(デフォルト32,768、上限384,000)、そして入力ウィンドウ1,048,576トークンです。入力ウィンドウには、システムプロンプトや以前のツール結果を含め、送信するすべてのメッセージが数えられます。
SpicyAPI は、フィールドを黙って捨てることはありません。400 エラーがフィールド名を示し、課金は発生しません。DeepSeek 自身の API は thinking モードで temperature とペナルティ系フィールドを受け付けたうえで無視するため、古いクライアントがそれに気づかないまま持ち越している場合があります。
静かに起こる失敗はモデル名です。DeepSeek 自身の API では V4 Flash は提供終了となり、deepseek-v4-flash は一時的に V4.1 Flash に振り向けられています。現在の名前は deepseek-flash です。V4 Pro は事情が異なります。DeepSeek は当初9月14日に Flash へ切り替えると発表しましたが、その後変更履歴で、V4 Pro のサービスは課金も変えずに継続すると確認されました。以前の Pro 呼び出しがすべて Flash で動いているものとして移行を進めないでください。
SpicyAPI では、DeepSeek の3つのチャットルートはそれぞれ別の公開契約を持っています。設定に deepseek/v4-flash/chat を残したままでも有効なリクエストですが、出力上限は65,536トークンで、価格は高くなります。ネイティブサービスのエイリアスは、このホスト型の契約を変えません。今回の移行では deepseek/v4.1-flash/chat を明示的に指定してください。
このルートは image_url パートによる画像は受け付けますが、PDF/ファイルのパートは受け付けません。PDF は送る前にテキストを抽出してください。以下のテキストのみの修理メモの例では、画像入力は不要です。
応答に余裕を持たせる
モデルは答える前に推論し、その推論は max_tokens から消費されます。デフォルトの effort は high で、上限を120トークンにしたところ、内容がまったく返りませんでした。デフォルトの max_tokens は32,768で、それより長い成果物は途中で切れます。上限は、見える回答に数千トークンの思考用の余裕を足した大きさにし、384,000の上限に合わせないでください。
抽出は Low の effort から始めます。必要な区別を落とすようなら、すべての行を高くするのではなく、そのレコードにだけ High を試します。大きな入力にはストリーミングインターフェースを使います。
まず Playground で試す
- DeepSeek V4.1 Flashを開き、Playground を選んで、effort のコントロールを Low にします。
- 下のスクリプトにある JSON 形式の指示を System prompt に、修理メモ R17 を Message to the model に入力し、Run model をクリックします。
- アプリを移行する前に、承認待ちが完了済みの修理に変わっていないことを確認します。
高くつく間違いを見つけるテストを実行する
修理メモは、テストの題材として適しています。流れの良い要約が、お金のかかる形で間違うことがあるためです。「交換を推奨」は顧客の承認ではなく、承認は完了した作業ではありません。
API Keys のキーを SPICY_API_KEY に設定し、次のスクリプトを check_repair.py として保存して、python3 check_repair.py で実行してください。システムプロンプトには JSON の形が示され、DeepSeek の JSON モードガイドが求める「json」という語が含まれています。
import json
import os
import uuid
import urllib.request
import re
SOURCE = (
'Repair note R17: "Inspected the rear brake pads. Recommend replacement before '
'the next long ride. Customer approval is pending." No later work record is supplied.'
)
KEYS = {"inspected", "recommended_action", "authorized", "completed", "evidence"}
REQUIRED_QUOTES = (
"Inspected the rear brake pads.",
"Recommend replacement before the next long ride.",
"Customer approval is pending.",
)
def check(response: dict) -> list[str]:
choice = response["choices"][0]
problems = []
if choice.get("finish_reason") != "stop":
problems.append(f"finish_reason is {choice.get('finish_reason')!r}: the reply may be cut off")
content = (choice.get("message") or {}).get("content")
if not content or not content.strip():
return problems + ["empty content: reasoning may have used the whole max_tokens budget"]
try:
data = json.loads(content)
except json.JSONDecodeError as error:
return problems + [f"not valid JSON: {error}"]
if not isinstance(data, dict):
return problems + ["JSON must be an object"]
if missing := KEYS - data.keys():
problems.append(f"missing keys: {sorted(missing)}")
for key in ("inspected", "authorized", "completed"):
if data.get(key) is not None and type(data.get(key)) is not bool:
problems.append(f"{key} must be true, false or null")
if data.get("inspected") is not True:
problems.append("inspected should be true")
if data.get("authorized") is not False:
problems.append("authorized must be false: approval is pending")
if data.get("completed") is not None:
problems.append("completed must be null: no completion record is supplied")
action = data.get("recommended_action")
if not isinstance(action, str) or not action.strip():
problems.append("recommended_action must be a non-empty string")
else:
words = set(re.findall(r"[a-z]+", action.lower()))
# A lexical screen, not a proof of semantic equivalence.
required = {"rear", "long", "ride"}
compatible = (required <= words
and bool(words & {"replace", "replacement", "replacing"})
and bool(words & {"pad", "pads"})
and not words & {"not", "no", "never", "front", "after"})
if not compatible:
problems.append("review recommendation wording against R17")
quotes = data.get("evidence")
if not isinstance(quotes, list) or not quotes:
problems.append("evidence must be a non-empty list of quotes")
else:
problems += [f"invalid or unmatched quote: {q!r}" for q in quotes
if not isinstance(q, str) or not q.strip() or q not in SOURCE]
for expected in REQUIRED_QUOTES:
if not any(isinstance(q, str) and expected in q for q in quotes):
problems.append(f"missing supporting quote: {expected}")
return problems
body = {'model': 'deepseek/v4.1-flash/chat',
'messages': [{'role': 'system',
'content': 'Extract only facts stated in the source. Reply with one '
'JSON object shaped like {"inspected": null, '
'"recommended_action": null, "authorized": null, '
'"completed": null, "evidence": []}. inspected, '
'authorized and completed are true, false or null; use '
'null for anything the source does not state. evidence '
'lists exact quotes from the source that support every '
'non-null value.'},
{'role': 'user',
'content': 'Repair note R17: "Inspected the rear brake pads. '
'Recommend replacement before the next long ride. '
'Customer approval is pending." No later work record is '
'supplied.'}],
'max_tokens': 2048,
'reasoning_effort': 'low',
'response_format': {'type': 'json_object'}}
request = urllib.request.Request(
"https://api.spicyapi.ai/v1/chat/completions",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + os.environ["SPICY_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4())},
)
with urllib.request.urlopen(request, timeout=90) as reply:
response = json.load(reply)
problems = check(response)
print(json.dumps(response.get("usage", {})))
if problems:
raise RuntimeError("; ".join(problems))
print(response["choices"][0]["message"]["content"])期待される判断は、点検済み、承認は保留のまま、完了は不明、です。推奨内容には、後輪のブレーキパッドと「次の長距離ライドの前」という期限が残っている必要があります。
承認と完了を別々のテストとして追加する
承認待ちのケースだけで終わらせないでください。以下を追加テストのひな形にし、それぞれを実行する前に SOURCE、REQUIRED_QUOTES、承認/完了の期待値を更新します。R17 用のチェッカーは、そのままでは R18 や R19 に対して実行できません。
| 最新の記録 | Authorized | Completed |
|---|---|---|
| R17: “Customer approval is pending.” 後続の記録なし | false | null |
| R18: “Customer approved the quoted replacement. Parts ordered; work scheduled for Thursday.” | true | null |
| R19: “Replaced pads; road check completed Thursday.” 承認済みの作業に紐づく | true | true |
作業ID、記録の順序、日付はソースと一緒に保持してください。R18 は承認待ちを上書きしますが、作業が予定されたことは完了の証拠にはなりません。日付のない「完了」のメモを同じ作業に紐づけられない場合は、R17 に統合せず、紐づけが未解決であることをそのまま返します。
この3段階の区別は、クリエイターの制作ワークフローにも現れます。「このキャラクターデザインを承認してください」「デザイン承認済み」「承認済みデザインから動画を生成した」は、それぞれ依頼、決定、実行にあたります。抽出プロンプトには、これらの段階を別々に、それぞれを裏づける記録とともに出力させ、メモ同士が食い違う場合はその食い違いを残してください。最も自信ありげな文をモデルに選ばせてはいけません。
ツールを使うワークフローでも承認と完了を分ける
承認の記録は、修理が行われた証拠にはなりません。ステータスを変える前に完了の記録を照会し、ツールが返す内容でも承認と完了は別のフィールドにしておきます。
ツールを使わないチャットには、追加の対応は不要です。リクエストに tools がある場合、アシスタントのメッセージをロールと内容だけで組み立て直すクライアントは、1ターン目は通っても2ターン目で 400 になります。そのため、1ターンだけのテストではこの不具合に気づけません。アシスタントのメッセージは、reasoning_content を含めて返ってきたとおりに追加し、そのあと呼び出しごとに tool メッセージを1つずつ、対応する tool_call_id を付けて追加して、ラウンド数には上限を設けます。テストは1ターンではなく2ターンで行ってください。チャットのレシピに完全なループがあります。
Agent に呼び出しを準備させる
レコード抽出を繰り返す場合は、コーディング用の Agent に SpicyAPI Skill を使わせます。“Read deepseek/v4.1-flash/chat, then adapt the example to my input; show the request before running it.” CLI での照会は spicyapi models get deepseek/v4.1-flash/chat --json、MCP では spicyapi_model_get に model: "deepseek/v4.1-flash/chat" を指定します。チャットの呼び出しには、互換クライアントかチャット APIを使います。メディアタスク用のコマンドは、チャットの応答を生成しません。
料金を計算し直す
料金は、キャッシュなしの入力が100万トークンあたり$0.30、キャッシュされた入力が100万トークンあたり$0.006、出力が100万トークンあたり$1.20 で、リクエストの長さによらず一律です。キャッシュなしの40,000トークンのプロンプトで出力が2,000トークンなら、$0.0144 です。入力トークンのうち30,000がキャッシュ、10,000がキャッシュなしとして課金されるなら、同じ呼び出しは $0.00558 になります。推論トークンは出力として課金されるため、見える回答が短くても、思った以上に出力を使うことがあります。課金に含めずに済むのは none だけです。
DeepSeek 自身の API から離れる場合は、2つの比較が重要です。DeepSeek の定価はピーク時には同じ$0.30、$0.006、$1.20ですが、オフピークは半額で、SpicyAPI は一律料金です。DeepSeek でオフピークに回すバッチは、同じトークンでも SpicyAPI では約2倍になります。また、キャッシュ料金は入力料金の50分の1ですが、適用されるのは呼び出しごとにバイト単位で完全に同一のプレフィックスだけです。1,500トークンのシステムプロンプトを10,000回呼ぶと入力は1,500万トークンで、キャッシュなしなら$4.50、すべてがキャッシュ読み取りだった場合でも多くても$0.09です。返された usage と精算後のコストを、これらの見積もりと比べてください。タイムスタンプや指示の並べ替えで共通のプレフィックスが崩れることがあり、入力が変わらないだけではキャッシュヒットは保証されません。
DeepSeek の各ルートの比較
| V4.1 Flash | V4 Flash | V4 Pro | |
|---|---|---|---|
| モデルID | deepseek/v4.1-flash/chat | deepseek/v4-flash/chat | deepseek/v4-pro/chat |
| 最大出力 | 384,000 | 65,536 | 384,000 |
| 入力/キャッシュ/出力(100万トークンあたり) | $0.30 / $0.006 / $1.20 | $0.44 / $0.014 / $1.32 | $1.32 / $0.044 / $3.96 |
parallel_tool_calls | 公開なし | 公開なし | 公開あり |
3つとも、入力は1,048,576トークン、effort は none、low、high(デフォルトは high)、デフォルトの max_tokens は32,768です。deepseek/v4-flash/chat から移る場合、リクエスト本文は同一でIDだけが変わり、出力上限はほぼ6倍になって、すべての料金が下がります。V4 Pro から移る場合は parallel_tool_calls が使えなくなり、入力は77%安く、出力は約70%安くなります。個々のタスクで V4 Flash と V4 Pro のどちらが向くかは別の判断で、DeepSeek V4 Flash vs Proで扱っています。
アプリが成人向けフィクションを書く場合
SpicyAPI では V4.1 Flash は検閲なしとして掲載されていますが、成人向けフィクションのリクエストをすべて完遂するという約束ではありません。2026年9月のテストでは、強い描写を直接求めるリクエストは毎回和らげられて返ってきました。一方、フィクションとして枠づけるか、システムプロンプトでその枠を設定すると、より多くが省略されずに書かれましたが、すべてではありませんでした(詳細は V4.1 Flash のレビュー)。直接的なリクエストを受け付けるモデル向けにプロンプトを書いていた場合は、移行時にその枠づけをシステムメッセージへ移し、登場人物はすべて成人にしてください。
移行時の失敗をその場で直す
| 症状 | 考えられる原因 | 対処 |
|---|---|---|
400 unsupported_parameter(temperature、top_p、ペナルティ、parallel_tool_calls、thinking のいずれかを名指しするもの) | 古いクライアントから引き継いだフィールド | 削除する |
content が空、または finish_reason が length | max_tokens を推論で使い切ったか、応答がそれを超えた | max_tokens を上げる、effort を下げる、再試行する |
| JSON は解析できるが、完了済みの作業として報告している | 推奨が完了として読まれた | フィールドを分け、引用を必須にし、チェッカーを実行する |
| ツール会話の2ターン目でエラー | アシスタントのメッセージを reasoning_content なしで組み立て直した | 返されたメッセージをそのまま追加する |
| 残高が足りているのに 402 | 40201 のホールドが残高を超えているか、40202 の支出上限に達した | エラーを確認する。ホールドのために大きすぎる出力予算を減らすか、キー/ワークスペースの上限を見直す |
再利用した Idempotency-Key が拒否される(400 または 409) | キーが完了済みのリクエストか、別の本文に属している | 完了済みのリクエストに対する 400 は応答を再現できず、409 は本文が変わったことを示す場合がある。キーを再利用するのは、有効期間内の同一のネットワーク再試行のときだけにする。新しい有料呼び出しを選ぶ前に、タスクの精算状況を確認する |
| ストリーミングしない長い呼び出しが約100秒で切れる | ネットワークの端での打ち切り | 応答をストリーミングする |
従来のベースラインを失わずに切り替える
設定には古いモデルIDを残し、両方でテストを実行します。新しいIDでテストが通り、自分のソースでの検証も通り、記録した usage が上の計算と一致したら、トラフィックを移してください。








