考える・探す・読むを、自分のコードでつなぐ
DeepSeek API には、このガイドで使う入口が 3 つあります。文章を生成する Chat Completions、Web を検索する Search API、1 ページを読み込む Fetch API です。どれも同じ API キーで呼び出せます。
Search API と Fetch API は、モデルとは独立した HTTP の API です。このガイドの方法(deepseek-flash + Chat Completions)では、モデルが勝手に検索することはありません。検索するかどうか、何を読ませるかは、あなたのコードが決めます。この役割分担を最初に押さえておくと、後の章が読みやすくなります。DeepSeek のサーバー側で検索する経路もあり、11 章で扱います。
①
deepseek-flash が何を指し、何が強みなのかを数字で説明できる。② Search / Fetch API を直接呼び、結果の採否を判断できる。③ 検索結果を根拠にした回答と、モデルが自分で検索するエージェントを動かせる。④ エラー・課金・レート制限への備えを説明できる。実習は 6 つです。最初の挨拶から始め、検索 → ページ取得 → 根拠つき回答 → 自分で検索するエージェント → エラー処理と進みます。API キーがなくても、同梱のモックサーバーで全実習を動かせます。
- LAB 1 最初の呼び出しで、思考モードの出力を見る
- LAB 2 Search API で検索し、結果の質を数値で判定する
- LAB 3 Fetch API でページを Markdown として読む
- LAB 4 検索結果だけを根拠に、出典つきで答えさせる
- LAB 5 モデルに検索ツールを渡し、自分で調べさせる
- LAB 6 再試行・空の結果・入力エラーを確かめる
「書く名前」と「応答するモデル」を区別する
DeepSeek API の model に書く値は、2026 年 10 月 20 日時点で deepseek-flash と deepseek-v4-pro の 2 つです。deepseek-flash には現在 DeepSeek-V4.1-Flash-1017 が応答します。名前は同じまま、裏のモデルが更新される仕組みです。2026 年 10 月 17 日の更新でも、呼び出し方法と価格は変わっていません。
model に書かない。 「DeepSeek-V4.1-Flash-1017」は応答するモデルの版名です。本ガイドで確認した公式ページには、deepseek-v4.1-flash のような API 名は出てきません。書く値は deepseek-flash です。また、Web 上の古い記事には deepseek-chat / deepseek-reasoner を使う例が残っています。この 2 つは 2026-04-24 の更新で「3 か月後(2026-07-24)に廃止」と告知され、現行の Quick Start と料金表には載っていません。旧名 deepseek-v4-flash と deepseek-v4-flash-vision-exp は今も受け付けます。ただし元のモデルは退役しており、V4.1-Flash-1017 が Flash 価格で応答します。新しく書くコードでは deepseek-flash を使いましょう。
Anthropic 形式の入口(https://api.deepseek.com/anthropic)では、Claude のモデル名を読み替えます。claude-opus で始まる名前は deepseek-v4-pro(V4 Pro 価格)、claude-haiku / claude-sonnet で始まる名前は deepseek-flash になります。claude で始まらない未知の名前は 400 エラーです。
半年の動き
V4-Pro の扱いについて、2026-09-10 のニュースページは「段階的に終了し、9 月 14 日から deepseek-v4-pro を V4.1-Flash へ転送する」と書いています。一方、同日の Change Log は「要望を受け、9 月 14 日以降も V4 Pro の API 提供を続ける」と書き、現行の料金表も V4-Pro-0813 を独自の価格で載せています。本ガイドは Change Log と料金表を現行の情報として扱います。
一次情報:Models & Pricing / Change Log / DeepSeek-V4.1-Flash Release / Anthropic API
1017 版で何が伸びたか
DeepSeek は 1017 版を「前版より強いベンチマーク、速いタスク完了、短い出力」と説明し、性能とコストの両方が良くなった(パレート改善)としています。モデルカードによれば、推論・コーディング・エージェント・視覚のベンチマークで改善し、タスクの実行時間と出力の長さも短くなりました。
この章の数値はすべて DeepSeek の公表値(2026-10-20 時点)です。第三者による検証ではありません。比較には Hugging Face のモデルカードの表を使います。同じ表に前版(2026-09-10 版)が並んでいるため、条件をそろえて比べられます。
11 項目すべてで 1017 版が前版を上回っています。差が大きいのは Terminal-Bench 4.0(+11.1)、ProgramBench(+8.8)、Apex Bench v1.1(+7.5)、HLE(+5.8)、Automation-Bench v1.0.6(+5.7)です。
他のモデルと並べると
同じモデルカードには、他社モデルとの比較も載っています。表の中で最も高い値を太字にしました。
| ベンチマーク | DS-V4.1-Flash-1017 | DS-V4.1-Flash | K3 | GLM-5.3 | GPT-5.6 Sol | Opus 5 |
|---|---|---|---|---|---|---|
| HLE w/o tools | 42.6(44.0†) | 36.8(39.1†) | 43.5 | 42.0† | 44.5 | 56.3 |
| Terminal-Bench 4.0 | 42.3 | 31.2 | 12.6 | 37.9 | 39.9 | 51.8 |
| DeepSWE v1.1 | 77.0 | 74.2 | 67.5 | 66.9 | 73.0 | 74.0 |
| ProgramBench | 29.1 | 20.3 | 17.5 | 19.0 | 23.0 | 37.0 |
| NL2Repo-Bench | 68.2 | 65.4 | 58.0 | 58.0 | 56.8 | 75.3 |
| Automation-Bench v1.0.6 | 60.5 | 54.8 | 46.7 | 48.8 | 45.8 | 50.3 |
| Agents’ Last Exam | 35.4 | 31.8 | 27.6 | 28.5 | 26.7 | 28.6 |
| Apex Bench v1.1 | 72.1 | 64.6 | 47.8 | 56.6 | 54.3 | 65.8 |
| Chartography (w/tools) | 83.0 | 78.9 | 68.1 | — | 79.9 | 84.0 |
| BabyVision (w/tools) | 92.5 | 89.6 | 85.7 | — | 88.9 | 94.1 |
| ZeroBench-main (w/tools) | 51.0 | 49.0 | 41.0 | — | 53.0 | 52.0 |
1017 版は、DeepSWE v1.1・Automation-Bench・Agents’ Last Exam・Apex Bench の 4 項目で表内の最高値です。Opus 5 は 6 項目で最高値、GPT-5.6 Sol は ZeroBench-main で最高値でした。V4.1-Flash は新しいアーキテクチャ系列で最小のモデルと説明されています。最小のモデルでありながら、公表値の上では比較対象を上回る項目と、まだ差がある項目の両方があります。
- † は HLE のテキストのみのサブセットでの値です。
- Terminal-Bench 4.0 は Claude Code、DeepSWE v1.1 は Mini-SWE-V2 というハーネス(評価用の実行環境)で測っています。Change Log には「ZeroBench-main は Pass@5」とも書かれています。
- Change Log の 2026-09-10 版の記載は「HLE」「Automation-Bench」で、1017 版の「HLE (w/o tools)」「Automation-Bench v1.0.6」と表記が違います。前版との比較は、同じ表に両方が並ぶモデルカードの値で行いました。
- 自分の用途での良し悪しは、自分のデータで試して判断します。
1017 版について、DeepSeek は「自己フィードバックによる強化学習で、ゲーム制作から 3D モデリングまでのプロジェクト開発を改善した」と説明し、故宮の 3D モデルや Web のレースゲームなどの作例を公開しています。また DeepSeek Harness・Tencent CodeBuddy・WorkBuddy 向けに最適化され、DeepSeek Harness は v0.2.1-rc.1 以降が推奨されています。
一次情報:DeepSeek-V4.1-Flash-1017(Hugging Face) / Change Log 2026-10-17 / DeepSeek-V4.1-Flash-1017 Release
強みを 6 つの数字でつかむ
安い単価と時間帯割引
入力(キャッシュミス)は 100 万トークンあたり $0.3、出力は $1.2(ピーク時)。オフピークは半額です。キャッシュヒットした入力は $0.006 で、ミスの 50 分の 1 です。
1M トークンの文脈
コンテキスト長は 1M、最大出力は 384K(料金表)。長い資料や、ツール結果が積み重なるエージェントの会話を 1 回の要求に収めやすくなります。
考える深さを選べる
思考モードは既定で有効で、深さ(effort)の既定は high です。low / high / max から選べ、Change Log(2026-08-13)は「簡単な作業は low、日常のエージェント作業は high、より複雑な場面は max」を目安に挙げています。料金は入出力のトークン数で決まり、DeepSeek は 1017 版で出力が短くなったと説明しています。ただし、思考のトークンがどう課金されるかは、本ガイドで確認したページには明記されていません。
既存のコードから移りやすい
OpenAI 形式・Anthropic 形式・Responses API の 3 形式に対応します。JSON Output、Tool Calls、Vision(画像入力)も使えます。Vision は deepseek-flash だけの機能です。
重みが公開されている
MIT ライセンスです(Hugging Face のモデルカードの表示)。重みは Hugging Face と ModelScope で公開されています。総パラメータ数は、2026-09-10 の発表では「552B の MoE(入力 8B・出力 16B がアクティブ)」(MoE は Mixture of Experts の略で、多数の専門家ネットワークのうち一部だけを動かして計算量を抑える構成)、Hugging Face のページ表示では 763B で、数え方の説明はありません。
多くの要求を同時に流せる
アカウントあたりの同時実行数の上限は 2,500(deepseek-v4-pro は 500)。9 月の発表では、KV キャッシュの容量を前世代の HBM 4 分の 1・SSD 8 分の 1 に圧縮したとしています。
料金表(100 万トークンあたり・USD・2026-10-20 時点)
| 項目 | flash オフピーク | flash ピーク | v4-pro オフピーク | v4-pro ピーク |
|---|---|---|---|---|
| 入力(キャッシュヒット) | $0.003 | $0.006 | $0.022 | $0.044 |
| 入力(キャッシュミス) | $0.15 | $0.3 | $0.66 | $1.32 |
| 出力 | $0.6 | $1.2 | $1.98 | $3.96 |
| Search / Fetch (単位が異なる:成功 1,000 件あたり) | それぞれ $0.75。直接呼ぶ場合はこの料金で、モデルの料金とは別(時間帯による区別の記載なし)。Harness / Claude Code / Codex 経由のサーバー側呼び出しは北京時間 2026-11-01 00:00(日本時間 01:00)まで無料で、以後は同じ Search / Fetch 料金(11 章) | |||
ピークは月〜金の 01:00–04:00 と 06:00–10:00(UTC)で、日本時間では月〜金の 10:00–13:00 と 15:00–19:00です。中国の祝日は終日オフピーク、週末もオフピークです。急がない一括処理はオフピークに回すと半額になります。
キャッシュ(ディスク上の文脈キャッシュ)は設定不要で、既定で有効です。要求の入力の終わりや出力の終わりなどで「キャッシュ単位(cache prefix unit)」が保存され、次の要求の先頭がその単位と完全に一致したときだけヒットします。たとえば 1 回目が A + B、2 回目が A + B + C なら、A + B の部分がヒットします。応答の usage にある prompt_cache_hit_tokens と prompt_cache_miss_tokens で、ヒットした量を確認できます。ヒット率は保証されず(ベストエフォート)、キャッシュの構築には数秒かかり、使われなくなると通常数時間〜数日で消えます。
月の利用量から費用を見積もる
| 内訳 | deepseek-flash | deepseek-v4-pro |
|---|
2026-10-20 時点の公表単価で計算します。ヒット率 80% は例示値なので、実際の値は usage の prompt_cache_hit_tokens から求めて入れてください。Search / Fetch は直接呼ぶ場合の料金です。Harness / Claude Code / Codex 経由のサーバー側呼び出しは北京時間 2026-11-01 00:00(日本時間 01:00)まで無料で、以後は同じ Search / Fetch 料金です。実際の請求は時間帯ごとの利用量で決まり、料金は改定されることがあります。
一次情報:Models & Pricing / Thinking Mode / Context Caching / Rate Limit & Isolation / Change Log 2026-08-13 / V4.1-Flash Release / V4.1-Flash-1017 Release(ModelScope) / Hugging Face
環境を準備し、最初の 1 回を呼ぶ
実習コードは Python 3.10 以上で動きます。使うライブラリは、Chat Completions 用の OpenAI SDK と、Search / Fetch 用の requests の 2 つです。DeepSeek API は OpenAI 形式と互換なので、OpenAI SDK の base_url を差し替えるだけで使えます。
openai==3.35.1
requests==2.34.2
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
PowerShell の実行ポリシーで Activate.ps1 が止められた場合は、先に Set-ExecutionPolicy -Scope Process RemoteSigned を実行し、確認を求められたら Y を入力します(そのウィンドウだけに効きます)。仮想環境を有効にした後は、どの OS でも python で実行します。新しいターミナルを開いたら、最初に仮想環境を有効にしてください。有効にしないまま実行すると ModuleNotFoundError になります。
API キーを用意する
DeepSeek Platform で API キーを発行し、環境変数 DEEPSEEK_API_KEY に入れます。キーをコードに直接書かないでください。
source .venv/bin/activate
export DEEPSEEK_API_KEY="<DeepSeek API Key>"
unset DEEPSEEK_BASE_URL # 既定の https://api.deepseek.com を使う
.venv\Scripts\Activate.ps1
$env:DEEPSEEK_API_KEY="<DeepSeek API Key>"
Remove-Item Env:DEEPSEEK_BASE_URL -ErrorAction SilentlyContinue
mock_server.py は、公式ドキュメントに書かれた入力検証・エラー形式・応答の形を再現したローカルサーバーです。料金はかからず、ネットワークにも出ません。応答の文面は固定の練習用です。source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
python mock_server.py # http://127.0.0.1:8787 で待ち受け
source .venv/bin/activate
export DEEPSEEK_BASE_URL=http://127.0.0.1:8787
export DEEPSEEK_API_KEY=test-key # モックは空でないキーなら受け付ける
.venv\Scripts\Activate.ps1
$env:DEEPSEEK_BASE_URL="http://127.0.0.1:8787"
$env:DEEPSEEK_API_KEY="test-key"
まず curl で形を見る
公式の Quick Start と同じ要求です。thinking で思考モードを有効にし、reasoning_effort で深さを指定しています。エンドポイントは /chat/completions で、/v1 は付きません。
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
-d '{
"model": "deepseek-flash",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
],
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": false
}'
共通部品を読む
全実習が使う部品を ds_client.py にまとめました。接続先は環境変数 DEEPSEEK_BASE_URL で切り替えます。モデル名は deepseek-flash だけを使います。
ds_client.py の全文を見る
"""DeepSeek API 用の共通部品。
Chat は OpenAI SDK、Search / Fetch は生の JSON API(requests)で呼ぶ。
01〜05 のスクリプトはすべてここを経由する。
"""
from __future__ import annotations
import os
import random
import sys
import time
from typing import Any
import requests
from openai import APIError, APIStatusError, OpenAI
# DEEPSEEK_BASE_URL が無ければ本番の既定値(/v1 は付けない)。
BASE_URL: str = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com").rstrip("/")
MODEL = "deepseek-flash"
HIGH_QUALITY_SCORE = 1.5 # 公式ガイド(Search & Fetch): rerank_score がこれ以上で高品質
HIGH_AUTHORITY = 3 # 公式ガイド(Search & Fetch): authority がこれ以上で高権威
# DeepSeekToolError.status の特別な値。
# 0: requests の接続エラー・タイムアウトで、そもそも HTTP ステータスが無い場合。
# -1: HTTP 200 だが、応答の形(JSON か・必要なキーがあるか)が期待と違う場合。
CONNECTION_ERROR_STATUS = 0
MALFORMED_RESPONSE_STATUS = -1
RETRY_AFTER_CAP_SECONDS = 30.0 # サーバー指定の待機時間が長すぎても CLI を固まらせないための上限
# テストから差し替えられるよう、time.sleep をモジュール変数として公開する。
sleep = time.sleep
class DeepSeekToolError(Exception):
"""Search / Fetch 呼び出しの失敗を表す例外。"""
def __init__(self, status: int, message: str, *, retryable: bool) -> None:
super().__init__(f"[{status}] {message}")
self.status = status
self.message = message
self.retryable = retryable
def get_api_key() -> str:
"""DEEPSEEK_API_KEY を返す。未設定なら日本語メッセージを出して終了コード 2。"""
key = os.environ.get("DEEPSEEK_API_KEY")
if not key:
print(
"環境変数 DEEPSEEK_API_KEY が設定されていません。"
"DeepSeek の API キーを設定してから実行してください。",
file=sys.stderr,
)
raise SystemExit(2)
return key
def chat_client() -> OpenAI:
"""Chat Completions 用の OpenAI クライアントを作る。"""
return OpenAI(api_key=get_api_key(), base_url=BASE_URL)
def _extract_error_message(resp: requests.Response) -> str:
try:
data = resp.json()
except ValueError:
return resp.text
if not isinstance(data, dict):
return resp.text
error = data.get("error")
if isinstance(error, dict):
return str(error.get("message", resp.text))
if isinstance(error, str):
return error
return resp.text
def format_tool_error(exc: DeepSeekToolError) -> str:
"""DeepSeekToolError を表示用の日本語 1 行メッセージにする(02/03/04 で共通に使う)。"""
if exc.status == CONNECTION_ERROR_STATUS:
# requests の接続エラー・タイムアウトには HTTP ステータスが無いので、
# 「HTTP 0」ではなく通信エラーであることをそのまま伝える。
return f"通信エラー: {exc.message}"
if exc.status == MALFORMED_RESPONSE_STATUS:
# HTTP 200 なので「HTTP 200」と言うと成功したように読めてしまう。
return f"応答の形式が不正です: {exc.message}"
return f"エラー(HTTP {exc.status}): {exc.message}"
def format_chat_error(exc: APIError) -> str:
"""openai の例外を表示用の日本語 1 行メッセージにする(01/04/05 で共通に使う)。"""
if isinstance(exc, APIStatusError):
body = exc.body
message = body.get("message") if isinstance(body, dict) else str(exc)
return f"エラー(HTTP {exc.status_code}): {message}"
# APIConnectionError など、ステータスコードを持たない例外はまとめて通信エラーとして扱う。
return f"通信エラー: {getattr(exc, 'message', str(exc))}"
def _backoff_delay(attempt: int, retry_after: str | None) -> float:
"""指数バックオフ(0.5s, 1s, 2s…)+ ジッター。Retry-After があれば優先(ただし 30 秒を上限にする)。"""
if retry_after is not None:
try:
return min(float(retry_after), RETRY_AFTER_CAP_SECONDS)
except ValueError:
pass
base = 0.5 * (2 ** (attempt - 1))
return base + random.uniform(0, base * 0.1)
def post_tool(path: str, payload: dict[str, Any], *, max_attempts: int = 4) -> dict[str, Any]:
"""Search / Fetch の生 JSON API を叩く。再試行つき。"""
key = get_api_key()
url = f"{BASE_URL}{path}"
headers = {"Authorization": f"Bearer {key}"}
attempt = 0
while True:
attempt += 1
try:
resp = requests.post(url, headers=headers, json=payload, timeout=60)
except (requests.ConnectionError, requests.Timeout) as exc:
if attempt >= max_attempts:
raise DeepSeekToolError(CONNECTION_ERROR_STATUS, str(exc), retryable=True) from exc
sleep(_backoff_delay(attempt, None))
continue
if resp.status_code == 200:
try:
return resp.json()
except ValueError:
raise DeepSeekToolError(MALFORMED_RESPONSE_STATUS, "JSON として解釈できませんでした", retryable=False)
message = _extract_error_message(resp)
if resp.status_code == 402:
# 残高不足は再試行しても解決しないため即座に送出する。サーバーのメッセージも付記する。
raise DeepSeekToolError(
402,
f"残高不足です。入金してから再試行してください(サーバー: {message})。",
retryable=False,
)
if resp.status_code == 408 or resp.status_code == 429 or resp.status_code >= 500:
if attempt >= max_attempts:
raise DeepSeekToolError(resp.status_code, message, retryable=True)
sleep(_backoff_delay(attempt, resp.headers.get("Retry-After")))
continue
# それ以外の 4xx は要求を変えずに再試行しない。
raise DeepSeekToolError(resp.status_code, message, retryable=False)
def search(
query: str,
*,
count: int = 5,
need_content: bool = False,
ori_question: str | None = None,
) -> list[dict[str, Any]]:
"""Search API を呼び、results を返す。"""
payload: dict[str, Any] = {"query": query, "count": count, "need_content": need_content}
if ori_question is not None:
payload["ori_question"] = ori_question
data = post_tool("/v1/tools/search", payload)
results = data.get("results")
if not isinstance(results, list):
raise DeepSeekToolError(MALFORMED_RESPONSE_STATUS, "results がありません", retryable=False)
return results
def fetch(url: str) -> dict[str, Any]:
"""Fetch API を呼び、{"title", "content"} を返す。"""
data = post_tool("/v1/tools/fetch", {"url": url})
if not isinstance(data.get("title"), str) or not isinstance(data.get("content"), str):
raise DeepSeekToolError(
MALFORMED_RESPONSE_STATUS, "title または content が文字列ではありません", retryable=False
)
return data
def is_high_quality(result: dict[str, Any]) -> bool:
"""rerank_score が高品質の基準を満たすか。"""
return result.get("rerank_score", 0) >= HIGH_QUALITY_SCORE
def is_authoritative(result: dict[str, Any]) -> bool:
"""authority が高権威の基準を満たすか。"""
return result.get("authority", -1) >= HIGH_AUTHORITY思考モードの出力を見る
"""最初の Chat Completions 呼び出し。
思考モードの reasoning_content(思考の過程)と content(最終回答)、
usage(トークン使用量)を表示する。
"""
from __future__ import annotations
import argparse
import sys
from typing import Any
from openai import APIError
from ds_client import MODEL, chat_client, format_chat_error
DEFAULT_QUESTION = "DeepSeek の Search API と Fetch API の違いを 2 文で説明してください。"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="DeepSeek Chat Completions の最初の呼び出し")
parser.add_argument("question", nargs="?", default=DEFAULT_QUESTION, help="質問文(省略可)")
parser.add_argument("--no-thinking", action="store_true", help="思考モードを無効にする")
return parser.parse_args()
def main() -> None:
args = parse_args()
client = chat_client()
messages = [
{"role": "system", "content": "あなたは日本語で簡潔に答えるアシスタントです。"},
{"role": "user", "content": args.question},
]
# 公式ガイド(Thinking Mode): OpenAI SDK では thinking を extra_body に入れる。
# 無効にするときは reasoning_effort を付けない。
kwargs: dict[str, Any] = {}
if args.no_thinking:
kwargs["extra_body"] = {"thinking": {"type": "disabled"}}
else:
kwargs["reasoning_effort"] = "high"
kwargs["extra_body"] = {"thinking": {"type": "enabled"}}
try:
response = client.chat.completions.create(model=MODEL, messages=messages, **kwargs)
except APIError as exc:
print(format_chat_error(exc), file=sys.stderr)
raise SystemExit(1)
message = response.choices[0].message
reasoning_content = getattr(message, "reasoning_content", None)
print("[思考]")
print(reasoning_content or "(思考なし)")
print()
print("[回答]")
print(message.content)
print()
usage = response.usage
print("[usage]")
print(f"prompt_tokens: {usage.prompt_tokens}")
print(f"completion_tokens: {usage.completion_tokens}")
# ディスクキャッシュのヒット/ミス(公式ガイドの KV Cache)。応答に無い場合がある。
hit = getattr(usage, "prompt_cache_hit_tokens", None)
miss = getattr(usage, "prompt_cache_miss_tokens", None)
if hit is not None:
print(f"prompt_cache_hit_tokens: {hit}")
if miss is not None:
print(f"prompt_cache_miss_tokens: {miss}")
if __name__ == "__main__":
main()
python 01_hello.py
python 01_hello.py --no-thinking
[思考]
「DeepSeek の Search API と Fetch API の違いを 2」について考えます。
[回答]
「DeepSeek の Search API と Fetch API の違いを 2 文で説明してください。」についてお答えします(モック応答)。
[usage]
prompt_tokens: 72
completion_tokens: 62
prompt_cache_hit_tokens: 0
prompt_cache_miss_tokens: 72
思考モードでは、答え(content)の前にモデルの考えた過程が reasoning_content として返ります。--no-thinking を付けると思考が無効になり、reasoning_content は返りません。
thinkingは SDK の通常の引数にないため、extra_body={"thinking": {...}}に入れます。reasoning_effortは通常の引数で渡せます。- 思考モードでは
temperature・presence_penalty・frequency_penaltyが効きません。エラーにもならないので、気づかずに調整してしまいがちです。 deepseek-flashのtop_pは思考モードでのみ有効で、0.95 未満は 0.95 として扱われます。非思考モードでは 1.0 に固定され、指定した値は無視されます。- effort に
mediumやxhighを指定するとhighに、minimalはlowに、ultraはmaxに読み替えられます。
一次情報:Your First API Call / Thinking Mode
Search API で Web を検索する
Search API は、検索語を受け取り、Web ページの一覧を JSON で返します。DeepSeek の自社検索エンジンが動いており、Change Log では「毎日数億件の検索を処理し、数千億ページを索引し、SEO スパム・GEO 操作・低品質な AI 生成コンテンツを厳しく除外する」と説明されています。
curl https://api.deepseek.com/v1/tools/search \
-H "Authorization: Bearer <DeepSeek API Key>" \
-H "Content-Type: application/json" \
-d '{
"query": "DeepSeek V4 release",
"count": 5,
"need_content": true
}'
リクエストの 5 項目
| 項目 | 型・既定値 | 意味 |
|---|---|---|
query | 文字列・必須 | 検索語。1〜128 文字。空白だけは不可。検索演算子を使える |
count | 整数・既定 10 | 返す件数。1〜50。料金には影響しない |
ori_question | 文字列・任意 | 検索のきっかけになったユーザーの元の質問(1〜128 文字)。検索語の解釈を助ける。null は未指定扱い |
localization | 文字列・既定 "" | 結果を地域に合わせるための IP アドレス |
need_content | 真偽値・既定 false | true でページ本文を content に入れる。false なら content は常に空文字 |
リクエスト本文に未知の項目があっても無視されます。count を範囲外にすると 400 エラーです。
検索演算子で絞り込む
query の中に演算子を書くと、サイト・言語・日付で絞り込めます。演算子はそのまま検索エンジンへ渡され、1 つの検索語で組み合わせられます。
| 演算子 | 例 | 意味 |
|---|---|---|
site: | DeepSeek site:github.com | そのサイトに限る。複数なら site:a OR site:b |
-site: | DeepSeek -site:example.com | そのサイトを除く(複数は空白区切り) |
- | DeepSeek -advertisement | その語を含むページを除く(複数は空白区切り) |
-"..." | DeepSeek -"release notes" | その句を含むページを除く(句は空白を含められる。複数は空白区切り) |
"..." | DeepSeek "exact phrase" | 句に完全一致(複数は空白区切り) |
lang: | DeepSeek lang:zh,en | 言語コードで絞る(カンマ区切り) |
after: | DeepSeek after:2025-01-01 | その日付より後に公開(年だけも可) |
before: | DeepSeek before:2026-06-01 | その日付より前に公開(年だけも可) |
演算子を組み立てて、要求の JSON を作る
site: の OR と -site: を同時に使う組み合わせは公式の例にないため、動作は確かめていません。
レスポンスの 10 項目
成功すると HTTP 200 で results 配列が返ります。一致するページがなければ空の配列です。1 件ごとに次の 10 項目があり、用途で 3 つに分けると覚えやすくなります。
| 項目 | 型 | 意味 |
|---|---|---|
rerank_score | 数値 | 関連性・権威性・鮮度などを合わせた 0〜3 のスコア。1.5 以上が高品質 |
authority | 整数 | 情報源の権威の段階。0〜4(-1 は不明)。3 以上は権威性が高い |
title / url | 文字列 | ページのタイトルと URL |
snippet | 文字列 | 検索語に関する短い要約。通常 100〜200 文字。画面表示向け |
summary | 文字列 | 検索語に関する長い要約。通常 600〜800 文字。モデルに読ませる向け |
content | 文字列 | ページ本文。need_content が false か、取得できなければ空 |
site_name / site_icon | 文字列か null | サイト名とアイコンの URL |
date | 文字列か null | 公開日時(UTC)。RFC 3339 という日時の書式で、例は 2026-06-01T00:00:00+00:00。不明なら null |
LLM に読ませる文章は、まず summary を使いましょう。検索語に合わせた通常 600〜800 文字の要約なので、1 件あたりの文字数が読みやすい量に収まります。need_content を true にすると本文も受け取れますが、Search の料金は要求 1 件単位で変わらない一方、モデルへ渡す文字が増えればその分の入力トークン料金がかかります。
検索して、結果の質を数値で判定する
"""Search API の呼び出しサンプル。
使い方:
python 02_search.py "DeepSeek site:github.com after:2026" --count 5 [--content]
"""
from __future__ import annotations
import argparse
import sys
from ds_client import DeepSeekToolError, format_tool_error, is_authoritative, is_high_quality, search
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="DeepSeek Search API を呼び出す")
parser.add_argument("query", help="検索クエリ(検索演算子を含められる)")
parser.add_argument("--count", type=int, default=5, help="取得件数(1〜50、既定 5)")
parser.add_argument("--content", action="store_true", help="本文(content)も取得する")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
results = search(args.query, count=args.count, need_content=args.content)
except DeepSeekToolError as exc:
# count=51 のような不正な引数のときも、トレースバックではなく短いメッセージで終わる。
print(format_tool_error(exc), file=sys.stderr)
raise SystemExit(1)
if not results:
print("該当なし(空の結果は課金されません)")
return
high_quality = 0
high_authority = 0
for result in results:
marks = ""
if is_high_quality(result):
marks += "★"
high_quality += 1
if is_authoritative(result):
marks += "◆"
high_authority += 1
date = result.get("date") or "-"
print(f"{marks:<2} rerank_score={result['rerank_score']:.1f} authority={result['authority']} date={date}")
print(f" {result['title']}")
print(f" {result['url']}")
print(f" {result['snippet']}")
if args.content:
print(f" 本文: {result['content'][:200]}")
print()
print(f"高品質 {high_quality} 件 / 高権威 {high_authority} 件 / 合計 {len(results)} 件")
if __name__ == "__main__":
main()
python 02_search.py "DeepSeek site:github.com after:2026" --count 5
python 02_search.py "DeepSeek V4.1 Flash" --count 3 --content
★◆ rerank_score=2.6 authority=4 date=2026-10-10T00:00:00+00:00
モック結果 1: DeepSeek site:github.com after:2026
https://example.com/mock/1
「DeepSeek site:github.com after:2026」に関する短い要約(モック、1 件目)。
…
高品質 3 件 / 高権威 3 件 / 合計 5 件
★ は rerank_score が 1.5 以上、◆ は authority が 3 以上の結果です。このサンプルの --count の既定は 5 です(API 自体の既定は 10)。2 行目の --content を付けると要求に need_content: true が入り、API が各結果の content に本文を入れて返します(取得できなかったページは空のまま)。サンプルは各結果の下に 本文: として先頭 200 文字を表示します。モックは count が 6 を超えても 6 件までしか返しません(実 API は最大 count 件)。検索語を変えて、★ と ◆ の数がどう変わるか見てください。
一次情報:Search & Fetch / Search API Reference / Change Log 2026-10-17
Fetch API でページを読む
Fetch API は、URL を 1 つ受け取り、そのページを Markdown に変換して返します。検索結果の要約では足りないとき、元のページを読ませるために使います。
curl https://api.deepseek.com/v1/tools/fetch \
-H "Authorization: Bearer <DeepSeek API Key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/article"
}'
{
"title": "Example article",
"content": "# Example article\n\nArticle content."
}
- リクエストは
urlの 1 項目だけです。8,192 文字以内で、ホスト名のある HTTP / HTTPS の URL を指定します。ユーザー名やパスワードを含む URL は使えません。 titleは取得できなければ空文字です。contentは Markdown のテキストです。- 取得できるかどうかは、対象サイトの稼働状況とアクセス方針に左右されます。
| ステータス | 意味 | 対応 |
|---|---|---|
| 403 | セキュリティポリシーで要求が拒否された | その URL は使わず、別の情報源を探す |
| 404 | 使える本文を取得できなかった(URL が無効、サイトの方針で遮断、対象サイトのエラー、未対応の形式、Markdown を抽出できない など) | 同じ URL で再試行せず、検索結果の summary で代用する |
| 429 | QPS(1 秒あたりの要求数)の上限を超えた。Fetch にはアカウント単位の 25 に加え、ドメイン単位の上限もある | 待ってから再試行する |
ページを Markdown として読む
"""Fetch API の呼び出しサンプル。
使い方:
python 03_fetch.py https://api-docs.deepseek.com/guides/search --chars 600
"""
from __future__ import annotations
import argparse
import sys
from ds_client import DeepSeekToolError, fetch, format_tool_error
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="DeepSeek Fetch API を呼び出す")
parser.add_argument("url", help="取得する URL")
parser.add_argument("--chars", type=int, default=600, help="本文を表示する先頭文字数(既定 600)")
return parser.parse_args()
def main() -> None:
args = parse_args()
try:
page = fetch(args.url)
except DeepSeekToolError as exc:
if exc.status == 403:
print("セキュリティポリシーで拒否されました。", file=sys.stderr)
elif exc.status == 404:
print("使える本文を取得できなかった(URL 不可・サイト方針・未対応形式など)。", file=sys.stderr)
else:
print(format_tool_error(exc), file=sys.stderr)
raise SystemExit(1)
print(f"[タイトル] {page['title'] or '(タイトルなし)'}")
print("[本文]")
print(page["content"][: args.chars])
if __name__ == "__main__":
main()
python 03_fetch.py https://api-docs.deepseek.com/guides/search --chars 600
python 03_fetch.py https://blocked.example/page # モックで 403 を体験
python 03_fetch.py https://missing.example/page # モックで 404 を体験
モックでは、ホスト名が blocked.example なら 403、missing.example なら 404 を返します。エラー時の表示と終了コードを確かめてください。
検索結果を根拠に、出典つきで答えさせる
モデルは学習した時点までの知識で答えます。最新の情報を答えさせるには、検索した資料を要求に入れ、「資料だけを根拠に答える」よう指示します。このように検索と生成を組み合わせる方法を RAG(検索拡張生成)と呼びます。ここではその最小形を作ります。
- 検索する。
ori_questionにユーザーの質問をそのまま渡すと、検索語の解釈を助けられます。queryとori_questionはどちらも 128 文字までなので、長い質問は先頭 128 文字に切って渡します。 - 選別する。
rerank_scoreが 1.5 以上の結果を優先し、上位 3 件に絞ります。高品質な結果が 1 件もなければ、警告を出したうえで上位 3 件を使います。結果が 0 件なら、モデルを呼ばずに終了します(空の結果は課金されません)。 - 資料を組む。結果ごとに番号・タイトル・URL・日付・
summaryを並べます。番号があると、回答の文末で出典を示せます。 - 答えさせる。system メッセージで「与えた資料だけを根拠に答え、資料にないことは分からないと書く」と指示します。
usage で確かめてください)。複数の資料を突き合わせて判断させたい場合は、思考モードを有効にして比べてください。出典つきで答えさせる
"""検索結果を文脈に入れて出典つきで回答させる、RAG(検索拡張生成)の最小形。
使い方:
python 04_grounded_answer.py "質問"
"""
from __future__ import annotations
import argparse
import sys
from openai import APIError
from ds_client import MODEL, DeepSeekToolError, chat_client, format_chat_error, format_tool_error, is_high_quality, search
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="検索結果を根拠に出典つきで回答する")
parser.add_argument("question", help="質問文")
return parser.parse_args()
def build_context(selected: list[dict]) -> str:
"""選んだ検索結果を `[番号] タイトル / URL / 日付 / 要約` の形で文脈テキストにする。"""
blocks = []
for i, result in enumerate(selected, start=1):
date = result.get("date") or "-"
blocks.append(f"[{i}] {result['title']}\nURL: {result['url']}\n日付: {date}\n要約: {result['summary']}")
return "\n\n".join(blocks)
def main() -> None:
args = parse_args()
if not args.question.strip():
print("質問が空です。1 文字以上の質問を指定してください。", file=sys.stderr)
raise SystemExit(2)
# 公式ガイド(Search & Fetch): query / ori_question はどちらも 1〜128 文字の制限があるため、
# 質問が長い場合は先頭 128 文字に切り詰めてから渡す(チャットに渡す質問文そのものは切らない)。
query = args.question[:128]
try:
results = search(query, count=8, ori_question=query)
except DeepSeekToolError as exc:
print(format_tool_error(exc), file=sys.stderr)
raise SystemExit(1)
if not results:
print("検索結果が 0 件でした(空の結果は課金されません)。質問を変えて再実行してください。")
raise SystemExit(1)
high_quality = [r for r in results if is_high_quality(r)]
if high_quality:
selected = high_quality[:3]
else:
print("警告: 高品質(rerank_score >= 1.5)の結果が無いため、上位 3 件をそのまま使います。")
selected = results[:3]
context = build_context(selected)
messages = [
{
"role": "system",
"content": (
"与えた資料だけを根拠に日本語で答え、文末に [番号] で出典を示してください。"
"資料に無いことは「資料からは分からない」と書いてください。\n\n" + context
),
},
{"role": "user", "content": args.question},
]
client = chat_client()
# 出典つきの短い要約タスクなので思考モードを無効にし、応答を速く・安くする
# (公式ガイド(Thinking Mode)の切替の実演。04 は 01/05 と対照的に非思考モードを使う)。
try:
response = client.chat.completions.create(
model=MODEL,
messages=messages,
extra_body={"thinking": {"type": "disabled"}},
)
except APIError as exc:
print(format_chat_error(exc), file=sys.stderr)
raise SystemExit(1)
print("[回答]")
print(response.choices[0].message.content)
print()
print("[出典]")
for i, result in enumerate(selected, start=1):
print(f"[{i}] {result['title']} ({result['url']})")
if __name__ == "__main__":
main()
python 04_grounded_answer.py "DeepSeek の Search API の料金体系は?"
回答の文末に [1] のような番号が付き、その下に出典一覧が表示されれば成功です。モックは count が 6 を超えても 6 件までしか返しません(実 API は最大 count 件)。実 API では、検索語を変えて出典の顔ぶれがどう変わるか確かめてください。
一次情報:Search & Fetch / Thinking Mode
モデルに、自分で調べさせる
前の章では、検索するかどうかをコードが決めていました。この章では、検索と取得をツールとしてモデルに渡し、いつ何を調べるかをモデルに決めさせます。Chat Completions の Tool Calls(関数呼び出し)を使います。
押さえておく原則は 1 つです。モデルは関数を実行しません。モデルが返すのは「この関数をこの引数で呼んでほしい」という依頼(tool_calls)です。実際に Search / Fetch API を呼び、その結果を会話に戻すのはあなたのコードです。
最重要ルール:reasoning_content を返す
tools を付けた要求では、それまでの全ターンの reasoning_content を返す。 思考モードでツールを使うとき、モデルの思考(reasoning_content)は次の要求で会話に連結され、モデルは前の思考の続きから考えます。ツールを呼ばなかったターンも含めて、assistant メッセージの reasoning_content を残して送り返してください。返さないと API は 400 エラーを返します。tools を付けない普通の会話では返送は不要で、送っても無視されます。公式サンプルは messages.append(response.choices[0].message) で、SDK が返したメッセージをそのまま履歴に追加しています。このメッセージには content・reasoning_content・tool_calls が含まれます。本ガイドの実習コードは、何を送っているかが見えるように、同じ内容の辞書を明示的に作っています。
messages が育っていく様子を 1 段ずつ見る
- 要求 1
- 応答 1
- 要求 2
- 応答 2
- 要求 3
- 最終応答
ツールの定義とループ
ツールは JSON Schema で定義します。web_search は検索語と件数、web_fetch は URL を受け取ります。ループは「モデルを呼ぶ → tool_calls があれば実行して結果を追加 → もう一度モデルを呼ぶ」をくり返し、tool_calls がない応答が来たら終わります。
- 上限を決める。モデルが検索をくり返して止まらない場合に備え、ステップ数の上限(既定 8、
--max-stepsで変更)を設けます。 - ツールの失敗は結果として返す。Fetch が 404 でも例外で止めず、
{"error": "..."}をツール結果として返します。モデルは別の URL や検索語を選べます。引数が JSON として読めない・オブジェクトでない・必須の項目(query/url)がない場合も、同じようにエラーとして返します。この実習ではツール定義にstrictを付けていないため、モデルの引数がスキーマどおりとは限らないからです。 - 引数を範囲に収める。ツール定義で
countを 1〜10 としていても、範囲外の値が来ることがあります。コード側で 1〜10 に丸めてから Search API を呼びます。 - 結果を絞って返す。検索結果はタイトル・URL・日付・権威性・スコア・
summaryに絞り、取得した本文は先頭 6,000 文字で切ります。文脈とトークンの節約になります。
自分で調べるエージェントを動かす
"""web_search / web_fetch を function tool としてモデルに渡すツール呼び出しエージェント。
使い方:
python 05_search_agent.py "質問" [--max-steps 8] [--follow-up "2 問目"]
"""
from __future__ import annotations
import argparse
import json
import sys
from typing import Any
from openai import APIError, OpenAI
from ds_client import MODEL, DeepSeekToolError, chat_client, format_chat_error
from ds_client import fetch as ds_fetch
from ds_client import search as ds_search
TOOLS = [
{
"type": "function",
"function": {
"name": "web_search",
"description": "Web を検索して関連ページの一覧を返す。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "検索クエリ(検索演算子を使える)"},
"count": {"type": "integer", "description": "取得件数(1〜10)", "minimum": 1, "maximum": 10},
},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "web_fetch",
"description": "指定した URL のページ本文を Markdown として取得する。",
"parameters": {
"type": "object",
"properties": {"url": {"type": "string", "description": "取得する URL"}},
"required": ["url"],
},
},
},
]
FETCH_CONTENT_LIMIT = 6000 # 文脈節約のため、取得した本文は先頭 6000 文字に切る
def assistant_message(msg: Any) -> dict[str, Any]:
"""公式サンプルの `messages.append(response.choices[0].message)` と等価な辞書を作る。
tools 付きの要求では、過去の assistant ターンの reasoning_content をすべて返送する
(返送しないと 400)。ツールを呼ばなかったターンも同じなので、空文字でもキー自体は入れる。
tool_calls が無ければキーごと省く。
"""
message: dict[str, Any] = {
"role": "assistant",
"content": msg.content or "",
"reasoning_content": getattr(msg, "reasoning_content", None) or "",
}
if msg.tool_calls:
message["tool_calls"] = [tc.model_dump() for tc in msg.tool_calls]
return message
def run_web_search(query: str, count: Any) -> tuple[str, str]:
"""web_search を実行する。戻り値は (モデルに返す JSON 文字列, 経過表示用の要約行)。"""
# スキーマでは count を 1〜10 に制限しているが、non-strict モードではモデルが
# 守らないことがある(999 を渡す、文字列で渡す、省略して None になる、など)ので丸める。
try:
count = int(count)
except (TypeError, ValueError):
count = 5
count = min(max(count, 1), 10)
try:
results = ds_search(query, count=count)
except DeepSeekToolError as exc:
return json.dumps({"error": exc.message}, ensure_ascii=False), f"エラー: {exc.message}"
trimmed = [
{
"title": r["title"],
"url": r["url"],
"date": r.get("date"),
"authority": r["authority"],
"rerank_score": r["rerank_score"],
"summary": r["summary"],
}
for r in results
]
return json.dumps(trimmed, ensure_ascii=False), f"検索結果 {len(trimmed)} 件"
def run_web_fetch(url: str) -> tuple[str, str]:
"""web_fetch を実行する。戻り値は (モデルに返す JSON 文字列, 経過表示用の要約行)。"""
try:
page = ds_fetch(url)
except DeepSeekToolError as exc:
return json.dumps({"error": exc.message}, ensure_ascii=False), f"エラー: {exc.message}"
content = page["content"][:FETCH_CONTENT_LIMIT]
trimmed = {"title": page["title"], "content": content}
return json.dumps(trimmed, ensure_ascii=False), f"取得: {page['title']}({len(content)} 文字)"
def summarize_messages(messages: list[dict[str, Any]]) -> str:
"""messages 配列を 1 行に要約する(--show-messages 用)。
assistant だけは reasoning_content の有無と tool_calls の件数を表示し、
2 ターン目以降も前のターンの reasoning_content が残っていることを確認できるようにする。
"""
parts = []
for message in messages:
role = message.get("role")
if role == "assistant":
has_reasoning = "あり" if message.get("reasoning_content") else "なし"
tool_call_count = len(message.get("tool_calls") or [])
parts.append(f"assistant(reasoning_content {has_reasoning}, tool_calls {tool_call_count})")
else:
parts.append(str(role))
return "送信 messages: " + " / ".join(parts)
def call_tool(name: str, arguments: Any) -> tuple[str, str]:
"""モデルが生成した引数を実行する。引数の形が壊れていても例外を投げず、エラーを結果として返す。"""
if not isinstance(arguments, dict):
message = "引数が JSON オブジェクトではありません"
return json.dumps({"error": message}, ensure_ascii=False), f"エラー: {message}"
if name == "web_search":
if "query" not in arguments:
message = "引数に query がありません"
return json.dumps({"error": message}, ensure_ascii=False), f"エラー: {message}"
return run_web_search(arguments["query"], arguments.get("count", 5))
if name == "web_fetch":
if "url" not in arguments:
message = "引数に url がありません"
return json.dumps({"error": message}, ensure_ascii=False), f"エラー: {message}"
return run_web_fetch(arguments["url"])
message = f"未知のツール: {name}"
return json.dumps({"error": message}, ensure_ascii=False), f"エラー: {message}"
def run_loop(
messages: list[dict[str, Any]],
client: OpenAI,
max_steps: int,
*,
turn: int,
show_messages: bool,
) -> bool:
"""ツール呼び出しループを 1 ターン分回す。最終回答に到達したら True を返す。"""
for step in range(1, max_steps + 1):
if show_messages:
# 2 ターン目以降でも、前のターンの assistant(reasoning_content あり) が
# messages に残ったまま送られていることをここで確認できる。
print(summarize_messages(messages))
try:
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}},
)
except APIError as exc:
print(format_chat_error(exc), file=sys.stderr)
raise SystemExit(1)
msg = response.choices[0].message
messages.append(assistant_message(msg)) # reasoning_content を必ず含める
reasoning = (getattr(msg, "reasoning_content", None) or "")[:120]
print(f"[turn {turn} / step {step}] 思考: {reasoning}")
if not msg.tool_calls:
print("[回答]")
print(msg.content)
return True
for call in msg.tool_calls:
try:
arguments = json.loads(call.function.arguments)
except json.JSONDecodeError:
# モデルの出力が壊れていても、例外で落とさずツール結果としてエラーを返す。
print(f"→ {call.function.name}(引数の JSON 解析に失敗)")
print("← エラー: 引数を JSON として読めませんでした")
error_result = json.dumps({"error": "引数を JSON として読めませんでした"}, ensure_ascii=False)
messages.append({"role": "tool", "tool_call_id": call.id, "content": error_result})
continue
print(f"→ {call.function.name}({arguments})")
result, summary_line = call_tool(call.function.name, arguments)
print(f"← {summary_line}")
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
print("ステップ上限に達しました。")
return False
def _positive_int(value: str) -> int:
"""argparse 用: 1 以上の整数のみを受け付ける。"""
parsed = int(value)
if parsed < 1:
raise argparse.ArgumentTypeError("1 以上の整数を指定してください")
return parsed
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="web_search / web_fetch を使うツール呼び出しエージェント")
parser.add_argument("question", help="質問文")
parser.add_argument("--max-steps", type=_positive_int, default=8, help="1 ターンあたりの最大ステップ数")
parser.add_argument("--follow-up", help="2 問目の質問(指定すると続けて 2 ターン目を実行する)")
parser.add_argument(
"--show-messages",
action="store_true",
help="各ステップで送信する messages 配列を 1 行要約で表示する",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
client = chat_client()
messages: list[dict[str, Any]] = [{"role": "user", "content": args.question}]
if not run_loop(messages, client, args.max_steps, turn=1, show_messages=args.show_messages):
raise SystemExit(1)
if args.follow_up:
# 前のターンの reasoning_content も messages に残したまま送り続ける。
messages.append({"role": "user", "content": args.follow_up})
if not run_loop(messages, client, args.max_steps, turn=2, show_messages=args.show_messages):
raise SystemExit(1)
if __name__ == "__main__":
main()
python 05_search_agent.py "DeepSeek の Fetch API の QPS 上限は?"
python 05_search_agent.py "DeepSeek の Fetch API の QPS 上限は?" --follow-up "Search API の上限は?" --show-messages
[turn 1 / step 1] 思考: ユーザーの質問に答えるため、まず web_search で情報を集めます。
→ web_search({'query': 'DeepSeek の Fetch API の QPS 上限は?', 'count': 3})
← 検索結果 3 件
[turn 1 / step 2] 思考: 検索結果の中から有力なページを 1 件取得します。
→ web_fetch({'url': 'https://example.com/mock/1'})
← 取得: Mock page(53 文字)
[turn 1 / step 3] 思考: 収集した資料を踏まえて最終回答をまとめます。
[回答]
収集した1 件の資料(例: Mock page)をもとに回答します(モック応答)。
各ステップで、モデルの思考の冒頭・呼んだツール・結果(検索の件数、取得したページのタイトルと文字数、またはエラー)が表示されます。--follow-up を付けると、同じ履歴に 2 問目を足してもう一度ループを回します(表示は turn 2)。--show-messages を付けると、モデルを呼ぶ直前に、送る messages の並びを 送信 messages: で始まる 1 行に要約して表示します。2 行目のコマンドを実行し、[turn 2 / step 1] の直前の行に、1 問目の assistant(reasoning_content あり, …) が残っていることを確かめてください。
送信 messages: user / assistant(reasoning_content あり, tool_calls 1) / tool / assistant(reasoning_content あり, tool_calls 1) / tool / assistant(reasoning_content あり, tool_calls 0) / user
--max-steps は 1 ターンあたりのステップ数の上限で、1 以上の整数を指定します(0 以下は使い方のエラーとして終了コード 2)。
発展:strict モードで引数の形を保証する
Tool Calls には strict モード(Beta)があります。base_url を https://api.deepseek.com/beta にし、すべての function に "strict": true を付けると、モデルの出す引数が JSON Schema に厳密に従います。思考モード・非思考モードのどちらでも使えます。object では全プロパティを required に並べ、additionalProperties を false にする必要があります。
エラー・上限・課金に備える
Search と Fetch は読み取り専用の API です。同じ要求を再送しても副作用はありません。そのため、ステータスコードを見て「待って再試行する」「直してから送る」を機械的に判断できます。
エラーの本文は OpenAI 互換の形で、param は常に null です。
{
"error": {
"message": "'count' must be between 1 and 50",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request_error"
}
}
実習コードの ds_client.post_tool() は、この図のとおりに動きます。408・429・5xx と通信エラーは最大 4 回まで試します。待ち時間は、応答に Retry-After ヘッダーがあればその秒数(ただし 30 秒が上限)、なければ間隔を倍々に広げます(指数バックオフ)。402 とそれ以外の 4xx は再試行せずにエラーを送出します。Chat の呼び出し(01・04・05)で API がエラーを返したり通信できなかったりしたときは、トレースバックではなく エラー(HTTP n): … または 通信エラー: … と表示して、終了コード 1 で終わります。
上限
| 対象 | 上限 | 補足 |
|---|---|---|
| Search | 100 QPS | アカウント単位。どの API キーでも合算。Fetch とは別枠 |
| Fetch | 25 QPS | アカウント単位に加え、取得先ドメインごとの上限もある(数値は非公開) |
deepseek-flash | 同時実行 2,500 | 要求を送ってから応答が終わるまでを 1 つと数える。拡張申請は無料 |
deepseek-v4-pro | 同時実行 500 | 同上 |
上限を超えると、どれも HTTP 429 が返ります。Search / Fetch の QPS はモデルの同時実行数とは別に数えられます。また、推論が始まらないまま 10 分たった要求は、サーバー側で接続が閉じられます。
課金のルール
- Search / Fetch は成功した要求 1 件ごとに課金されます(どちらも 1,000 件あたり $0.75)。トークン数や返った件数には関係なく、
countを増やしても 1 件の料金は同じです。 - 課金されるのは、HTTP 200 で結果がある場合だけです。Search は
resultsが空でないこと、Fetch は本文が返ったことが条件です。失敗・空の結果・タイムアウトは課金されません。 - モデルの料金は「トークン数 × 単価」です。付与された残高があれば、先にそちらから差し引かれます。
利用者ごとに分ける:user_id
1 つのアカウントで複数の利用者にサービスを提供する場合は、要求に user_id を付けると、コンテンツ安全・KV キャッシュ・スケジューリングを利用者ごとに分けられます。英数字・-・_ の 512 文字以内で、個人情報は入れません。OpenAI SDK では extra_body={"user_id": "..."} で渡します。
再試行・空の結果・入力エラーを確かめる
python 02_search.py "mock:429 DeepSeek" # 最初の 2 回は 429、3 回目で成功
python 02_search.py "mock:empty DeepSeek" # results が空(課金されない)
python 02_search.py "DeepSeek" --count 51 # count が範囲外 → 400、再試行しない
python 02_search.py "mock:402 DeepSeek" # 残高不足 → 402、再試行しない
python 01_hello.py "mock:chat400 テスト" # Chat API の 400 をエラー表示で確かめる
モックは検索語に mock:429 を含むと最初の 2 回だけ 429 を返し、mock:empty を含むと空の結果を返します。429 の回数は、モックを起動してからの通算で検索語ごとに数えます。もう一度試すときはモックを再起動してください。1 つ目は待ち時間のあとに結果が表示されれば、再試行が働いています。3 つ目は再試行せずに エラー(HTTP 400): 'count' must be between 1 and 50 と表示し、終了コード 1 で止まります。エラーの message に、直すべき項目が書かれていることを確かめてください。
ほかにも、検索語に mock:408 を含むと最初の 2 回だけ 408、mock:500 を含むと常に 500(4 回試して打ち切り)、mock:402 を含むと 402 を返します。Chat では、質問に mock:chat400 を含めると 400 になります。「余裕があれば」の 2 つは、それぞれ次のように表示して終了コード 1 で終わります。
エラー(HTTP 402): 残高不足です。入金してから再試行してください(サーバー: Insufficient Balance)。
エラー(HTTP 400): forced 400 for testing (mock:chat400)
一次情報:Search & Fetch — Errors / Billing / Rate Limits / Rate Limit & Isolation / Models & Pricing
Claude Code などのツールから使う
DeepSeek API は Anthropic 形式にも対応しているため、Claude Code のバックエンドとして使えます。公式の手順では、次の環境変数を設定してから claude を起動します。
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
export ANTHROPIC_AUTH_TOKEN=<your DeepSeek API Key>
export ANTHROPIC_MODEL=deepseek-flash[1m]
export ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-flash[1m]
export ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-flash[1m]
export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-flash
export CLAUDE_CODE_SUBAGENT_MODEL=deepseek-flash
export CLAUDE_CODE_EFFORT_LEVEL=max
export CLAUDE_CODE_AUTO_COMPACT_WINDOW=786432
Claude Code の Web Search にも DeepSeek API が対応しています。モデルが検索が必要と判断すると、DeepSeek の API 経由で検索します。このとき、検索結果を要約するための追加の LLM 要求が発生し、その分のトークン料金がかかります。検索そのものも Search API の課金対象です(北京時間 2026-11-01 00:00 から。それまでは無料)。
どの経路で検索できるか
- Responses API の
web_searchはdeepseek-v4-pro専用です。API がサーバー側で検索し、結果を文脈に加えて応答を続けます。deepseek-flashではエラーにならず無視されるため、「検索したつもりで検索されていない」ことが起こりえます。Flash で検索を使うなら、本ガイドのように自分のコードから Search / Fetch API を呼びます。 - Codex は Responses API でモデルと通信します。Codex 自体は
deepseek-flashでも使えます(セットアップスクリプトのメニュー 1)。公式の「Integrate with Codex」によると、Codex の組み込み Web 検索はdeepseek-v4-proでのみ使えます。セットアップスクリプトはメニュー 1 でdeepseek-flash、2 でdeepseek-v4-proを設定し、web_search = "disabled"を書き込みます。検索を使うには、下のように~/.codex/config.tomlを書き換えて Codex を再起動します。検索は DeepSeek のサーバー側で実行され、Search API の料金で課金されます。 - Claude Code は Anthropic 形式で接続します。Web Search へのネイティブ対応は公式ページに記載があり、Anthropic 形式の仕様表でも
server_tool_useとweb_search_tool_resultのブロックが「対応」です。モデルごとの制限は書かれていません。 - Claude Code の推奨設定は全枠が
deepseek-flashです。設定せずにclaude-opus系の名前が送られるとdeepseek-v4-pro(V4 Pro 価格)に読み替えられます。 - DeepSeek Harness は、エージェントのハーネス開発者向けに developer preview として公開されています。1017 版には v0.2.1-rc.1 以降が推奨されています。モデルごとにサーバー側検索を使えるかは記載がありません。
model = "deepseek-v4-pro"
web_search = "live"
一次情報:Integrate with Claude Code(Agent Integrations) / Integrate with Codex(Agent Integrations) / Change Log 2026-07-31・2026-08-13 / Responses API / Anthropic API / Search & Fetch — Billing / DeepSeek-V4.1-Flash-1017 Release
判断できるか確かめる
各問で答えを選ぶと、理由を表示します。迷ったら対応する章へ戻りましょう。
Q1. 最新の V4.1-Flash-1017 を使いたい。model に書く値は?
Q2. tools を付けた 2 回目の要求。前の assistant メッセージの reasoning_content は?
Q3. Search API が HTTP 200 で空の results を返した。料金は?
Q4. 再試行してよいのはどれ?
Q5. deepseek-flash で Responses API の web_search ツールを指定した。
発展課題:自分のサービスに組み込む
- LAB 4 の選別に
authorityを加え、「高品質かつ高権威」を優先する。 dateが古い結果を除く条件を、after:演算子とコード側の両方で試し、違いを比べる。- LAB 5 のツール結果に、検索 1 回あたりの料金の累計を表示する。
- オフピーク(ピークは日本時間の平日 10:00–13:00 と 15:00–19:00。中国の祝日を除く。それ以外の時間)に一括処理を回す仕組みを作る。
出典と、このガイドの検証範囲
仕様・数値・日付は、DeepSeek の公式ドキュメントと Hugging Face のモデルカードをもとにしています。確認日:。料金・無料期間・モデルの対応は、この日時点の情報です。
- Search & Fetch — Search / Fetch API のリクエスト・レスポンス・演算子・エラー・課金・レート制限
- Search API Reference / Fetch API Reference — 項目の型と制約
- Your First API Call / Models & Pricing — モデル名、料金、機能対応
- Change Log / DeepSeek-V4.1-Flash-1017 Release / DeepSeek-V4.1-Flash Release — 更新履歴、公表ベンチマーク
- DeepSeek-V4.1-Flash-1017(Hugging Face) — 比較表、ライセンス、推奨パラメータ
- Thinking Mode / Tool Calls / Context Caching — 思考モード、reasoning_content、関数呼び出し、キャッシュ
- Rate Limit & Isolation — 同時実行数、user_id
- Responses API / Anthropic API / Integrate with Claude Code(Agent Integrations) / Integrate with Codex(Agent Integrations) — 他形式とツール連携
- StellarArc design-system — カラー、タイポグラフィ、文書レイアウト、inline SVG 規定
検証済み:実習コード(Python)を同梱のモックサーバーに対して実行し、次の項目を自動テスト(30 件。モックサーバーに対する実行と、部品単位のテストを含む)で確認しました。要求の形(thinking・reasoning_effort・Search の各項目の値、--content での need_content と 本文: の表示)、再試行(429 と 408 は 3 回目で成功、500 は 4 回で打ち切り、402 と 400 は再試行しない、Retry-After の待ち時間は 30 秒が上限。これは部品単位のテストで、モックは Retry-After を返さない)、空の結果、Fetch の 403 / 404、reasoning_content の返送(2 ターン目を含む)、ターン番号と --show-messages の表示、--max-steps の検証、不正なツール引数と count の丸め込み、長い質問の切り詰め、エラー時にトレースバックを出さないこと(02・04 の Search のエラー、01 の Chat API のエラー)、API キー未設定時の終了コードです。Chat の通信エラー(接続できない場合)の表示は自動テストの対象外です。モックは公式ドキュメントに書かれた入力検証・エラー形式・「tools 付きの要求で reasoning_content がなければ 400」の規則を再現しています。
未検証:教材作成時に実際の DeepSeek API は呼び出していません(API キーを使わずに作成したため)。実 API の応答文面・検索結果の中身・処理時間はモックと異なります。DEEPSEEK_API_KEY を設定すれば、同じコードで実 API に対して実行できます。
数値の扱い:ベンチマークは DeepSeek の公表値です。総パラメータ数は公式情報の間で表記が一致しないため、断定していません。
用語
- トークン
- モデルが文章を数える単位。料金はトークン数で決まる。
- 思考モード
- 答える前に考える過程を出力するモード。過程は
reasoning_contentに入る。 - Tool Calls
- モデルが「この関数を呼んでほしい」と依頼する仕組み。実行するのは利用者のコード。
- RAG
- 検索した資料を要求に入れて答えさせる方法。
- QPS
- 1 秒あたりの要求数。
- キャッシュヒット
- 要求の先頭が保存済みのキャッシュ単位と完全に一致し、安い単価で処理された入力。
- MoE
- Mixture of Experts。多数の専門家ネットワークのうち一部だけを動かして計算量を抑えるモデル構成。
- RFC 3339
- 日時の書き方の規格。例
2026-06-01T00:00:00+00:00。
ブラウザの実習チェックは localStorage に保存します。このページは外部の API へ通信しません。Web フォントの読み込みには Google Fonts を使い、通信できない場合はローカル書体へ切り替わります。
