Strands AgentsのToolに"Tavily"を活用して高度なWEB検索を行う
はじめに
こんにちはFusicの大宮です🫡🙇💪
前回、「Googleのカスタム検索エンジンを使用してWEB検索を行う」という内容で記事を書きました。
今回は StrandsAgentsにWEB検索を行わせるための手段として"Tavily"を活用してみました。
結論として、GoogleCSMよりもLLMとの相性が良く、高機能で使い勝手が良かったです。
結論に行き着いた調査・検証結果をまとめました。
Tavilyとは
- AIエージェント向けの安全でモジュール化されたWebインテリジェンス
Tavilyは、LLMエージェント向けに特別に設計されたAPIファーストのWebインテリジェンスレイヤーで、リアルタイム検索、高忠実度コンテンツ抽出、構造化Webクロールを実現します。AIベースシステムを構築する開発者向けに構築されたTavilyは、精度、速度、モジュール性を重視して設計されています。Strands Agentsなどのエージェントフレームワークとのシームレスな統合エクスペリエンスを提供します。TavilyのAPIは、大手AI企業から信頼されているエンタープライズグレードのインフラストラクチャレイヤーです。堅牢な機能と、本番環境レベルの運用保証を兼ね備えています。
引用:https://aws.amazon.com/jp/blogs/machine-learning/build-dynamic-web-research-agents-with-the-strands-agents-sdk-and-tavily/
StrandsのToolにTavilyを採用する理由
上に引用した内容がそのままですが、下記のような内容が挙げられます。
- LLMエージェント向けに特別に設計されたAPIファーストのWebレイヤーである点。
- 多様な機能(リアルタイム検索、高忠実度コンテンツ抽出、構造化Webクロールを実現)
- AIシステムに適合した設計(精度、速度、モジュール性を重視して設計)
※正直、単純にAgentに安定したWEB検索をさせるための外部APIが思いつかないから汗
※最近になってやっとTavilyを知りました。
AWSはTavilyを採用する理由について以下のように記述してます。
- 共通のビジョン– Tavily と AWS はどちらも、エンタープライズ対応、セキュリティ、プライバシーを重視し、次世代の AI ベースのビルダーにサービスを提供しています。
- マーケットプレイス統合– Tavily はAWS マーケットプレイスで利用可能であり、エンタープライズ顧客にとってシームレスな統合と調達を実現します。
- ウェブアクセスの頼れるパートナー– AWS は、Strands Agents SDK 内でリアルタイム検索を統合するための最高のツールとして Tavily を選択し、エージェント開発者に最高のウェブアクセス エクスペリエンスを提供しました。
- Amazon Bedrock – Amazon Bedrock は、Meta、Anthropic、AI21、Amazon などの大手 AI 企業が提供する高性能 FM の選択肢を提供する、完全に管理された安全なサービスです。
実装
概要を理解したところで、実装に移っていきましょう。
空のStrands Agentsに対してTavilyを使用したToolを定義していきます。
シンプルなエージェントを構築
Strands Agentsのインストール
pip install strands-agents
さっそくAgent開発
# agent.py
from strands import Agent, tool
agent = Agent(tools=[]) # toolsはこのあと追加
input = input('質問を入力:')
agent(input)
⚠️注意点⚠️
aws configureでアカウント情報を設定
・Bedrockのモデルアクセスから,"claude 4 sonnet"をリクエストしたアカウント&権限があることを確認
・リクエストしたリージョンと同一であることを確認
即実行💨
python agent.py
$python3 agent.py
質問を入力:こんにちは
こんにちは!お手伝いできることがありましたら、お気軽にお尋ねください。インターネット検索や特定の情報を調べたいことなど、何でもお聞きください。
これで基盤ができました。
次にTavilyの環境変数を設定します。
環境変数の設定
Tavily api keyの取得
公式サイトにログインして作成します。
環境変数に設定
.envファイルを作成し、取得したAPIキーを設定
TAVILY_API_KEY=<取得したAPIキー>
Toolの作成
やっと本題です!
@tool イテレーションを使用して独自にToolを作成し、Tavily APIにアクセスすることでWEB検索を実行します。
agent部分とTool部分を分けたいので、agent.pyと同じディレクトリにtools/ ディレクトリを作成し、web_search.py
を作成しましょう。
コード全体
まずはコード全体を示して、その後に各部分の説明を行います。
# tools/web_search.py
import os
import logging
import threading
from typing import List, Dict, Any
from tavily import TavilyClient
from strands import tool
logger = logging.getLogger(__name__)
ALLOWED_TIME_RANGES = {"d", "w", "m", "y"}
_client: TavilyClient | None = None
_client_lock = threading.Lock()
def init() -> None:
_ensure_client()
def _ensure_client() -> TavilyClient:
global _client
if _client is not None:
return _client
with _client_lock:
if _client is None:
api_key = os.getenv("TAVILY_API_KEY")
if not api_key:
raise RuntimeError("環境変数 TAVILY_API_KEY が設定されていません")
_client = TavilyClient(api_key=api_key)
logger.info("TavilyClient initialized")
return _client
def _format_results(resp: Dict[str, Any]) -> str:
results = resp.get("results", [])
if not results:
return "検索結果はありません。"
lines: List[str] = []
for i, r in enumerate(results, 1):
title = r.get("title") or "(no title)"
url = r.get("url") or ""
content = (r.get("content") or "").strip()
if len(content) > 300:
content = content[:300] + "..."
lines.append(f"[{i}] {title}\nURL: {url}\n{content}\n")
return "\n".join(lines).rstrip()
@tool
def web_search(
query: str,
time_range: str | None = None,
include_domains: list[str] | None = None,
):
"""Tavily を用いたウェブ検索ツール。
Args:
query: 検索クエリ
time_range: 'd','w','m','y' のいずれかで期間フィルタ
include_domains: 絞り込みたいドメインのリスト
Returns:
Strands Agent 互換の辞書 (status/content)
"""
if not query or not query.strip():
return {"status": "error", "content": [{"text": "query は必須です"}]}
if time_range and time_range not in ALLOWED_TIME_RANGES:
return {"status": "error", "content": [{"text": f"time_range は {ALLOWED_TIME_RANGES} のいずれか"}]}
try:
client = _ensure_client()
resp = client.search(
query=query,
max_results=10,
time_range=time_range,
include_domains=include_domains,
)
formatted = _format_results(resp)
return {"status": "success", "content": [{"text": formatted}, {"json": resp}]}
except Exception as e:
return {"status": "error", "content": [{"text": f"検索失敗: {type(e).__name__}: {e}"}]}
各コード解説
Tavily Clientの確保(準備)
- 明示的な初期化処理(agent.pyから呼び出す)
- 取得したキーを設定、存在しなければエラーを返す。
def init() -> None:
_ensure_client()
def _ensure_client() -> TavilyClient:
global _client
if _client is not None:
return _client
with _client_lock:
if _client is None:
api_key = os.getenv("TAVILY_API_KEY")
if not api_key:
raise RuntimeError("環境変数 TAVILY_API_KEY が設定されていません")
_client = TavilyClient(api_key=api_key)
logger.info("TavilyClient initialized")
return _client
出力のフォーマッターを作成(必須でない)
- レスポンスを整形
def _format_results(resp: Dict[str, Any]) -> str:
results = resp.get("results", [])
if not results:
return "検索結果はありません。"
lines: List[str] = []
for i, r in enumerate(results, 1):
title = r.get("title") or "(no title)"
url = r.get("url") or ""
content = (r.get("content") or "").strip()
if len(content) > 300:
content = content[:300] + "..."
lines.append(f"[{i}] {title}\nURL: {url}\n{content}\n")
return "\n".join(lines).rstrip()
Tool自体の作成
-
_ensure_client()
でClient確保 - クエリ、期間フィルタ、ドメインを引数にとる(Agentが判断)
resp = client.search(
query=query,
max_results=10,
time_range=time_range,
include_domains=include_domains,
)
- ツールをいつ使用すべきか、その機能を明確に説明。
- 関数シグネチャで型ヒントを使用して、パラメータ、戻り値の型、およびデフォルト値を記述。
- 各パラメータの詳細と、受け入れられる形式の例を示す。
@tool
def web_search(
query: str,
time_range: str | None = None,
include_domains: list[str] | None = None,
):
"""Tavily を用いたウェブ検索ツール。
Args:
query: 検索クエリ
time_range: 'd','w','m','y' のいずれかで期間フィルタ
include_domains: 絞り込みたいドメインのリスト
Returns:
Strands Agent 互換の辞書 (status/content)
"""
if not query or not query.strip():
return {"status": "error", "content": [{"text": "query は必須です"}]}
if time_range and time_range not in ALLOWED_TIME_RANGES:
return {"status": "error", "content": [{"text": f"time_range は {ALLOWED_TIME_RANGES} のいずれか"}]}
try:
client = _ensure_client()
resp = client.search(
query=query,
max_results=10, # 環境変数,定数で管理しましょう笑
time_range=time_range,
include_domains=include_domains,
)
formatted = _format_results(resp)
return {"status": "success", "content": [{"text": formatted}, {"json": resp}]}
except Exception as e:
return {"status": "error", "content": [{"text": f"検索失敗: {type(e).__name__}: {e}"}]}
Agent部分の更新
- 上で作成したweb_searchツールと初期化関数をImport
- Agent() にImportしたToolを与える。(Agentは"WEB検索"のアクションが実行可能になる)
# agent.py
from strands import Agent
from dotenv import load_dotenv
load_dotenv()
from tools.web_search import web_search, init as init_web_search
init_web_search()
agent = Agent(tools=[web_search])
user_input = input('質問を入力:')
agent(user_input)
検証
GoogleCSEを使用したAgentとTavilyを使用したAgentで同じ質問に答えてみる。
質問1
最近のFusicの生成AIに関連する開発事例について3つ教えてください。
質問2
FusicのAWS関連の開発事例を4つ教えてください。
システムプロンプト
Tool名以外は同一にする。
あなたはWEB検索アシスタントです。[web_search / get_fusic_solutions] Toolを使用して検索結果を150文字以内に要約して質問に答えてください。
1. GoogleCSE(開発事例を検索するToolのみ与えたStrandsAgents)
回答1出力:
時間:29秒
回答2出力:
時間:18秒
===
2. Tavilyを使用した今回のAgent
回答1出力:
時間:15秒
回答2出力:
時間:13秒
結果
- 応答速度:Tavily側Agentの方が早い。
- 再現性:CSEが高い。
- 内容:どちらも間違った情報は出力していない。
- 出力結果が異なるのは、CSE側にはURL制限がかかっているためだと推測
まとめ
今回の調査・検証では、Strands AgentsにWeb検索機能を統合する手段としてTavilyが非常に有効であることが明らかになりました。
Tavilyを採用するメリット
LLMとの高い親和性: TavilyはAIエージェント向けに特別に設計されており、LLMが検索結果をより効果的に活用できるよう、精度、速度、モジュール性を重視しています。
豊富な機能と使いやすさ: リアルタイム検索、コンテンツ抽出、Webクロールといった多様な機能を提供し、シンプルなAPIを通じてエージェントフレームワークにシームレスに統合できます。
パフォーマンスの優位性: 応答速度の比較検証では、Tavilyを使用したエージェントがGoogleカスタム検索エンジン(CSE)を使用したエージェントよりも高速な応答を返す結果となり、LLMエージェントのパフォーマンス向上に寄与することが示されました。
今後
- Tavily が Strands Agentsの組み込みToolとして実行可能になりそうです。
- 現状,実装例はREADMEにありますが、Module Not Found になってしまう状態でした。
数行で高度なWEB検索を行うAgent構築が可能になる!楽しみです☺️
おわりに
長々した記事になりましたが、丁寧に実装について説明できました。
様々な検索手段を自律的に使い分ける検索ゴテゴテAgentを作ってみようとワクワクしているのですが、
WEB検索枠は"Tavily"一択だと思いました!
今後の組み込みToolも楽しみですね。
では!
Discussion