スケジュール実行で「Claude Code の直近の変更点を3行で要約して」と投げたところ、返ってきたのは数ヶ月前の機能を並べた、それらしいけれど古い要約でした。モデルは学習時点の知識で答えるので、これは想定どおりの挙動です。問題は、古いと気づかずに記事の下書きへ流れていきそうになったことでした。
公式の変更履歴ページは毎週のように更新されます。そこを Claude 自身に読ませてしまえば、要約は常に最新の本文に基づきます。これを叶えるのが web_fetch ツールです。Web 検索ではなく、こちらが指定した URL の本文(および PDF)を丸ごと取得してコンテキストに載せる、サーバー側で実行される道具です。
私自身、Dolice Labs で複数のサイトを個人開発で自動更新しているのですが、「公式ページの一次情報をそのまま読む」工程を入れてから、生成の事実誤りが目に見えて減りました。ここでは、その組み込みで実際につまずいた点を中心にまとめます。
web_fetch と web_search は役割が違う
最初に混同しやすいのが web_search との違いです。web_search は「クエリで世界中を検索して候補を集める」道具で、web_fetch は「すでに分かっている1枚のページを開いて全文を読む」道具です。
判断はシンプルで、コンテキストに URL が出ているなら fetch、まだ URL が分かっていないなら search という分け方になります。Claude は次のような時に fetch を選びます。
ユーザーメッセージや直前のツール結果に URL が含まれている
特定のページ(ある記事・README・料金ページ等)を名指ししていて、かつ web_search も有効になっていて先に検索で探せる
逆に、「REST API 設計のベストプラクティスは?」のような一般的な問いでは fetch は走りません。特定のページを指していないからです。この線引きを理解しておくと、「URL を渡しているのに fetch してくれない」「逆に毎回検索に行ってしまう」といった挙動の食い違いを切り分けやすくなります。
まずは最小実装で動かす
Python SDK での最小構成は驚くほど短いです。ツール配列に web_fetch を1つ足すだけで、取得から読解までモデル側が引き受けます。
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model = "claude-opus-4-8" ,
max_tokens = 1024 ,
messages = [
{
"role" : "user" ,
"content" : "次のページの要点を3つにまとめてください: "
"https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool" ,
}
],
tools = [
{
"type" : "web_fetch_20250910" ,
"name" : "web_fetch" ,
"max_uses" : 3 ,
}
],
)
for block in resp.content:
if block.type == "text" :
print (block.text)
ベータ機能のため、環境によってはベータヘッダ web-fetch-2025-09-10 を付ける必要があります。SDK のバージョンが新しければツール定義を渡すだけで通ることもありますが、unsupported 系のエラーが返るときは、まずヘッダの有無を疑うのが早道です。
ここで覚えておきたいのは、web_fetch がサーバーツールである点です。tool_use を受け取ってこちらが実行して結果を返す、いわゆるクライアントツールとは違い、取得そのものは Anthropic 側で完結します。こちらのコードでは HTTP リクエストを書きません。その代わり、後述するように「どの URL を読ませるか」の制御責任がこちら側に残ります。
レスポンスは3層構造で読む
返ってくる content 配列は、おおよそ次の順番で並びます。読解コードを書くときは、この3層を意識すると迷いません。
text — 「このページを読みます」というモデルの宣言
server_tool_use — 取得リクエスト(input.url に対象 URL)
web_fetch_tool_result — 取得結果。本文は content.content(document ブロック)に入る
実際の取得結果はこのような形です。
{
"type" : "web_fetch_tool_result" ,
"tool_use_id" : "srvtoolu_01234567890abcdef" ,
"content" : {
"type" : "web_fetch_result" ,
"url" : "https://example.com/article" ,
"content" : {
"type" : "document" ,
"source" : {
"type" : "text" ,
"media_type" : "text/plain" ,
"data" : "ページの本文..."
},
"title" : "Article Title"
},
"retrieved_at" : "2026-06-15T01:30:00Z"
}
}
PDF の場合は source.type が base64、media_type が application/pdf になり、本文ではなく base64 データが入ります。要約用途なら気にせずそのまま使えますが、取得した本文を自前で保存・解析したい時は、テキストと base64 で分岐が必要になる点だけ頭に置いてください。retrieved_at も地味に重要で、web_fetch はキャッシュを挟むため、ここが古ければ「最新を読んだつもりが少し前の版だった」ことに気づけます。
一番ハマるのは「URL を会話に出しておく」制約
ここが、ドキュメントを流し読みすると見落としやすい最大の落とし穴です。web_fetch は、会話のコンテキストに一度も登場していない URL を取得できません 。
具体的には、取得できる URL は次のいずれかに限られます。
ユーザーメッセージに書かれた URL
クライアント側ツールの結果に含まれる URL
直前の web_search / web_fetch の結果に出てきた URL
つまり、Claude がプロンプトの文脈から「たぶんこの URL だろう」と推測して組み立てた URL は弾かれます。これはデータ持ち出し(exfiltration)を防ぐための仕様で、悪意ある入力に「秘密情報をクエリに埋めた URL を取得させる」攻撃を成立させないための線引きです。
私が最初に書いたコードは、まさにここで詰まりました。記事のスラッグからカテゴリページの URL を Claude に組み立てさせ、それを fetch させようとしたのです。結果は url_not_allowed。対処はシンプルで、読ませたい URL は必ずこちらがユーザーメッセージに明示的に書く ことです。検索から始めたい場合は web_search を併用し、その結果に出た URL を fetch させます。
resp = client.messages.create(
model = "claude-opus-4-8" ,
max_tokens = 4096 ,
messages = [
{
"role" : "user" ,
"content" : "Claude Code の最新リリースノートを探して、"
"最も新しい1件の変更点だけを引用付きで整理してください" ,
}
],
tools = [
{ "type" : "web_search_20250305" , "name" : "web_search" , "max_uses" : 3 },
{
"type" : "web_fetch_20250910" ,
"name" : "web_fetch" ,
"max_uses" : 5 ,
"citations" : { "enabled" : True },
},
],
)
この「検索 → 候補選定 → 全文取得 → 引用付き回答」の流れは、URL を手元に持っていない自動運用と相性が良いです。ページを名指しできるなら fetch 単体、まだ場所が分からないなら search と組み合わせる、と覚えておくと設計に迷いません。
トークンと攻撃面を同時に絞る
web_fetch の利用自体には追加料金がかかりません。費用はあくまで「取得した本文がコンテキストに載った分のトークン」です。逆に言えば、大きなページを無防備に読むと一気にトークンを食います。公式の目安では、100 kB 級のドキュメントページで約 25,000 トークンに達します。
ここで効くのが max_content_tokens です。取得本文がこの上限を超えると切り詰められるので、私は要約用途では 8,000 前後に設定しています。25,000 トークン級のページなら取り込みを約 32% に圧縮できる計算で、月次のスケジュール実行を何十本も回す身としては、この一行の有無で費用がはっきり変わります。
あわせて使いたいのが allowed_domains と max_uses です。前者は取得先を信頼できるドメインだけに固定し、後者は1リクエストあたりの取得回数に天井を設けます。
tools = [
{
"type" : "web_fetch_20250910" ,
"name" : "web_fetch" ,
"max_uses" : 5 ,
"allowed_domains" : [ "platform.claude.com" , "docs.claude.com" ],
"max_content_tokens" : 8000 ,
"citations" : { "enabled" : True },
}
]
注意点として、allowed_domains と blocked_domains は同時に指定できません。どちらか一方です。信頼できる取得先が決まっている自動運用なら、ホワイトリスト方式の allowed_domains を推奨します。読ませたくないページを後から塞ぐ blocked_domains よりも、初めから許可制にしておくほうが事故が起きにくいと感じています。
エラーは 200 で返ってくる
もう一つの落とし穴が、エラーの返り方です。web_fetch の取得失敗は HTTP のエラーステータスではなく、200 のレスポンス本文の中に web_fetch_tool_error として入ってきます。例外ハンドラだけ書いて満足していると、失敗に気づけません。
def extract_fetch_errors (resp):
errors = []
for block in resp.content:
if getattr (block, "type" , None ) != "web_fetch_tool_result" :
continue
inner = block.content
if getattr (inner, "type" , None ) == "web_fetch_tool_error" :
errors.append(inner.error_code)
return errors
codes = extract_fetch_errors(resp)
if codes:
# 自動運用ではここでログに残し、必要なら別ドメインで再試行する
print ( "web_fetch 失敗:" , codes)
返りうる error_code は把握しておく価値があります。url_too_long(URL が 250 文字超)、url_not_allowed(ドメイン制限や URL 検証で拒否)、url_not_accessible(取得先が HTTP エラー)、too_many_requests(レート超過)、unsupported_content_type(テキストと PDF 以外)、max_uses_exceeded、それに内部エラーの unavailable です。
実運用でよく見たのは unsupported_content_type と「本文が空に近い」ケースです。web_fetch は JavaScript で動的に描画されるページに対応していません。クライアント側でレンダリングする SPA を読ませると、骨組みだけ取れて中身が薄い結果になります。対象が動的ページだと分かっているなら、web_fetch ではなく素直に別の取得経路を選ぶのが現実的です。
大きな文書を読むなら動的フィルタリング
長い PDF や巨大なドキュメントから「特定の節だけ」を抜きたい場面では、新しいツール版 web_fetch_20260209 が効きます。こちらは Claude がコードを書いて、取得した本文をコンテキストに載せる前に絞り込みます。必要な箇所だけ残して捨てるので、トークン消費を抑えつつ精度を保てます。
resp = client.messages.create(
model = "claude-opus-4-8" ,
max_tokens = 4096 ,
messages = [
{
"role" : "user" ,
"content" : "次の長い PDF から、料金に関する記述だけを抜き出してください: "
"https://example.com/whitepaper.pdf" ,
}
],
tools = [
{ "type" : "web_fetch_20260209" , "name" : "web_fetch" },
{ "type" : "code_execution_20250825" , "name" : "code_execution" },
],
)
動的フィルタリングはコード実行ツールの有効化が前提です。また、対応モデルが限られる点にも注意してください(Fable 5・Opus 4.8 などの新しい世代が対象です)。逆に、取得するページが最初から小さいなら、フィルタリングの恩恵は薄く、構成も複雑になります。私の使い分けは、「ページが数 kB 程度なら従来版 web_fetch_20250910、研究論文 PDF のように数十万トークン級なら web_fetch_20260209」です。道具の世代が新しいことより、取り込み量が大きいかどうかで選ぶほうが、結果も費用も読みやすくなります。
次の一歩
まずは手元の信頼できるドキュメント1ページを allowed_domains で固定し、max_content_tokens を 8,000 に絞った最小構成で要約させてみてください。そこで retrieved_at とエラーコードの両方をログに出すようにしておくと、本番に載せたときの挙動が一気に読めるようになります。仕様の細部は更新されるので、ツール版やベータヘッダは Anthropic 公式ドキュメント で都度確認するのが確実です。
一次情報をモデルに直接読ませる工程は、地味ですが事実性への効き目が大きい部分でした。同じように自動生成の精度で悩んでいる方の役に立てば嬉しいです。