エージェントフレームワークを使えば、モデルにWebツールを持たせるのは簡単です。しかし、Webにアクセスしやすくなるわけではありません。LangChain、CrewAI、AutoGenのエージェントがある程度の量のページを取得し始めると、すぐに403エラー、CAPTCHAページ、別の国向けのコンテンツが集まり始め、モデルはそれらをすべて本物の情報であるかのように平然と要約してしまいます。エージェントのWebツールをAIエージェント向けに設計されたプロキシ経由にすればアクセスの問題は解決しますが、それは正しい場所に組み込んだ場合に限ります。
このガイドでは、その組み込み方を解説します。各フレームワークのWebツールをResidential Proxy経由にする方法、モデルのAPIをプロキシから外す方法、そしてタスクごとにスティッキーセッションとローテーションを選ぶ方法を紹介します。まだプロキシの種類を選んでいる段階なら、まずWebを閲覧するAIエージェントに最適なプロキシの比較をご覧ください。この記事では、すでに種類を選んでいることを前提としています。
ルール1:プロセスではなくツールにプロキシを設定する
プロキシを追加する最も手っ取り早い方法は、HTTPS_PROXYをexportして再起動することです。エージェントのプロセスでは、これをしないでください。OpenAI、Anthropic、その他ほとんどのモデルSDKは同じ環境変数を読み込むため、すべてのモデル呼び出しもレジデンシャルネットワークを経由することになります。その結果、推論のステップごとにレイテンシが増え、トークンのためにプロキシのトラフィックを消費し、それでいて何の見返りもありません。モデルのAPIはIPでブロックしないからです。
各ツールが使うHTTPクライアントやブラウザに、プロキシを明示的に設定してください。それ以外はすべて通常の接続のままにします。ライブラリが環境変数しか読み込まない場合は、モデルのホストをNO_PROXYに追加してください。
プロキシURL
Shifterのゲートウェイでは、ターゲティングとセッションはユーザー名で指定します。1つのエンドポイントp.shifter.io:443ですべての国をカバーします。
import os, uuid
USER = os.environ["SHIFTER_USER"]
PASSWORD = os.environ["SHIFTER_PASS"]
def proxy_url(country: str = "us", session: str | None = None, ttl: int = 600) -> str:
"""Rotating by default; pass a session id to keep one exit IP for a task."""
username = f"{USER}-country-{country}"
if session:
username += f"-sid-{session}-ttl-{ttl}"
return f"http://{username}:{PASSWORD}@p.shifter.io:443"
def new_session() -> str:
return uuid.uuid4().hex[:12]
以下のすべての例で、このヘルパーを再利用します。
LangChain
LangChainのエージェントの多くは、2つの方法のいずれかでWebを読み込みます。検索用のドキュメントローダーか、クリックや入力を行うエージェント向けのブラウザツールキットです。
ドキュメントローダー。 WebBaseLoaderはproxies辞書を受け取り、取得するすべてのページでそれを使用します。
from langchain_community.document_loaders import WebBaseLoader
proxy = proxy_url(country="de")
loader = WebBaseLoader(
["https://shop.example/de/product/123"],
proxies={"http": proxy, "https": proxy},
)
docs = loader.load()
ローダーにはローテーションが適しています。各ページは独立しているため、リクエストごとに新しいIPを使えば負荷が分散されます。
ブラウザツールキット。 Playwrightツールキットは実際のブラウザを操作します。プロキシを設定してブラウザを自分で起動し、それをツールキットに渡します。
from langchain_community.agent_toolkits import PlayWrightBrowserToolkit
from playwright.sync_api import sync_playwright
session = new_session()
playwright = sync_playwright().start()
browser = playwright.chromium.launch(proxy={
"server": "http://p.shifter.io:443",
"username": f"{USER}-country-us-sid-{session}-ttl-1800",
"password": PASSWORD,
})
tools = PlayWrightBrowserToolkit.from_browser(sync_browser=browser).get_tools()
ここではセッションIDが重要です。閲覧するエージェントは、移動し、読み、クリックし、また戻ってきます。ステップの合間にIPが変わると、サイトからはセッションの途中で訪問者が都市から都市へ移動したように見えます。これはまさにボット対策が探しているパターンです。エージェントの実行ごとにセッションIDを1つにすれば、タスク全体を通じて出口が1つに保たれます。
CrewAI
CrewAIのエージェントはツールを使います。最もすっきりした方法は、独自のHTTPクライアントを持つ小さなカスタムツールを作ることです。
import requests
from crewai.tools import BaseTool
class FetchPage(BaseTool):
name: str = "fetch_page"
description: str = "Fetch a public web page as seen from a given country."
def _run(self, url: str, country: str = "us") -> str:
proxy = proxy_url(country=country)
r = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
r.raise_for_status()
return r.text[:20000]
このツールは調査を行うエージェントに渡し、執筆やレビューだけを行うエージェントには渡さないでください。5つのエージェントからなるクルーで、5つすべてがプロキシを必要とすることはまずありません。
モデルにcountryを選ばせると、市場調査のクルーで役立ちます。調査担当のエージェントは、追加のコードなしで、あるページが米国、ドイツ、日本でそれぞれどう表示されるかを比較できます。モデルが存在しないコードを作り出さないよう、許可する国のリストをツールの説明に記載しておきましょう。
AutoGen
AutoGenのAgentChatでは、ツールは普通のPython関数なので、プロキシはその中のクライアントに設定します。AutoGenのイベントループには非同期クライアントが適しています。
import httpx
from autogen_agentchat.agents import AssistantAgent
async def fetch_page(url: str, country: str = "us") -> str:
"""Fetch a public web page through a residential IP in the given country."""
async with httpx.AsyncClient(proxy=proxy_url(country=country), timeout=30) as client:
r = await client.get(url, follow_redirects=True)
r.raise_for_status()
return r.text[:20000]
researcher = AssistantAgent(
name="researcher",
model_client=model_client, # your model client, not proxied
tools=[fetch_page],
)
モデルクライアントは別に作成され、プロキシを一切通りません。これがルール1の要点です。
タスクごとにスティッキーかローテーションかを選ぶ
これを決めるのはフレームワークではなく、タスクです。簡単な目安は次のとおりです。
| エージェントのタスク | セッション | 理由 |
|---|---|---|
| 多数の独立したページの取得 | ローテーション | リクエストを分散でき、保持する状態がない |
| 複数ステップの閲覧、フォーム、カート | スティッキー、実行ごとにID 1つ | サイトは1人の訪問者を想定している |
| 数時間にわたるログイン状態のワークフロー | ISP Proxies | 請求期間を通じて同じ都市とISP |
| 国ごとの比較 | ローテーション、呼び出しごとに1か国 | 呼び出しごとにそれぞれが1つの市場 |
トレードオフについて詳しくは、プロキシIPをローテーションする判断基準をご覧ください。ログイン状態の長いセッションには、請求期間を通じて同じ都市とISPを維持するISP Proxiesが、アカウントを開いたままにするエージェントに適しています。
失敗をモデルに見えるようにする
エージェントは、自分がブロックされたことに気づくのが苦手です。CAPTCHAページも、要約すべきテキストのひとつにすぎません。2つの小さな習慣で誤った回答を防げます。
- 本文を返すのではなく、403と429で例外を発生させる。 フレームワークがエラーをモデルに返すので、モデルはブロックページをもとに推論するのではなく、再試行するか報告できます。
- 同じセッションではなく、新しいセッションで再試行する。 スティッキーセッションがブロックされたら、再試行用に新しいセッションIDを生成します。同じ出口で再試行しても、たいてい同じ結果になります。
再試行には上限を設けてください。URLごとに3回で十分です。それ以上は、ターゲットがすでに拒否すると決めたトラフィックに料金を払うことになります。
コスト管理
エージェントはループすることがあります。同じページを読み直し続けるプランナーは、トークンを消費するのと同じ速さでプロキシのトラフィックを消費します。上の例のように、ページ本文はモデルに渡す前に切り詰め、取得したページは実行の間キャッシュし、ツール呼び出しごとのバイト数をログに記録してください。そうすれば、暴走したエージェントに請求書で気づく前に、ダッシュボードで気づけます。
よくある質問
LangChainはプロキシに対応していますか?
はい。WebBaseLoaderなどのドキュメントローダーはproxies引数を受け取ります。ブラウザツールキットは起動したブラウザをそのまま使うので、プロキシはブラウザに設定します。
LLMのAPI呼び出しもプロキシを経由させるべきですか?
いいえ。モデルのAPIはIPでブロックしません。プロキシを経由させるとレイテンシが増え、トラフィックが無駄になります。プロキシはWebツールにのみ設定し、ライブラリが環境変数を読み込む場合はモデルのホストをNO_PROXYに追加してください。
AIエージェントにはローテーションとスティッキーセッションのどちらが適していますか?
独立したページの取得にはローテーション、複数ステップの閲覧には実行ごとにセッションIDを1つ使うスティッキー、数時間続くログイン状態のワークフローにはISP Proxiesが適しています。
エージェント自身に国を選ばせることはできますか?
はい。countryをツールの引数として公開し、許可するコードをツールの説明に記載してください。ゲートウェイは各呼び出しをその国経由でルーティングします。
まとめ
プロキシはプロセスではなくツールに設定しましょう。ページが独立している場合はローテーションを、エージェントが訪問者のように振る舞う場合は実行ごとに1つのスティッキーセッションを使い、モデルがブロックをコンテンツと取り違えないよう、ブロックをはっきり検知できるようにしてください。