はじめに
Strands Agents SDK を使って「クエリ分解」と「クエリ拡張」を行うツールを定義し、生成 AI の回答精度向上を図る方法を紹介します。Strands Agents における「ツール」はエージェントの機能を拡張するためのメカニズムです。ユーザーが自然言語で入力したクエリに基づいて、エージェント自身がツールを「いつ」「どのように」呼び出すかを決定します。
今回の記事では、「クエリ分解」と「クエリ拡張」を行うツールを Python モジュールとして定義し、エージェントがそれらを利用してユーザーの質問に対する回答を生成するエージェントの実装を行います。
参考情報
クエリ分解とクエリ拡張
クエリ分解とクエリ拡張は、生成 AI の回答精度向上を図るための手法です。クエリ分解は、複雑なクエリをそれ単体で回答可能な複数の簡単なクエリに分解する手法です。クエリ拡張は、もとのクエリに関連する用語や同義語を追加することで、検索範囲を適切に広げる手法です。それぞれ、以下のような特徴があります。
クエリ分解
クエリ分解は、複雑なクエリをそれ単体で回答可能な複数の簡単なクエリに分解する手法です。例えば、以下のようにクエリを分解します。
- もとのクエリ:「現状の生成 AI が抱える課題にはどのようなものがあるか」
- 分解後のクエリ:
- 「生成 AI の開発・運用にかかるコストや経済的課題は何か」
- 「生成 AI の環境負荷や持続可能性に関する課題は何か」
- 「生成 AI のデータセキュリティと個人情報の保護に関する課題は何か」
検索を行う際は、もとのクエリと分解後のクエリで検索し、その結果を組み合わせることで最適な回答を導き出すことが期待できます。
クエリ拡張
クエリ拡張は、もとのクエリに関連する用語や同義語を追加することで、検索範囲を適切に広げる手法です。例えば、以下のようにクエリを拡張します。
- もとのクエリ:「現状の生成 AI が抱える課題にはどのようなものがあるか」
- 拡張後のクエリ:
- 「生成 AI 規制 法規制 ガイドライン AI 規制法 ハルシネーション 著作権 プライバシー 倫理 エネルギー消費」
- 「生成 AI 情報リテラシー リテラシー格差 倫理 著作権 セキュリティ 透明性」
検索を行う際は、もとのクエリに拡張したクエリを追加して検索します。もとのクエリに関連する用語や同義語、異なる表現で書かれた関連文書も検索にヒットするようになり、より網羅的な検索結果が期待できます。
環境構築
Python 環境の準備
コードの実行環境は以下のとおりです。uv のインストールや venv の作成手順は省略します。
- Python 3.12.3
- uv 0.8.17
コードの配置は以下のとおりです。Strands Agents の Quick Start を参考に、以下のようなディレクトリ構造を作成します。
カレントワーキングディレクトリの ./tools 以下に配置したツールは、エージェントの初期化時に自動的に読み込まれ、そして自動的に再読み込みされます(Auto-loading and reloading tools)。ただし、この機能はデフォルトで無効になっているので、エージェントの初期化時に load_tools_from_directory=True
をセットして有効化します。この機能を使わない場合は、ツールを配置したディレクトリに __init__.py
の作成、import 文の追加、明示的なツール呼び出しが必要です。ここでは、自動読み込みを利用します。
.
├── agent.py
├── requirements.txt
└── tools
├── queries_decomposition.py
└── queries_expansion.py
strands-agents
strands-agents-tools
strands-agents-builder
Python パッケージをインストールします。
uv pip install -r requirements.txt
ツールの定義
クエリ拡張とクエリ分解を行うツールを定義します。それぞれ、以下のように定義します。ここでは、ツールをPythonモジュールとして定義します。その場合、次の2つのコンポーネントが必要です。
- TOOL_SPECツールの名前(name)、説明(description)、入力スキーマ(inputSchema)を定義する変数
- ツールの機能を実装する、ツール仕様で指定された名前と同じ名前の関数(def queries_expansion(), def queries_decomposition())
クエリ分解ツール
クエリ分解ツールの定義は以下のとおりです。queries_decompositionという名前でツールを定義するので、関数名も同じ名前を持つqueries_decomposition()を定義します。
クエリ分解ツールの定義を行うサンプルコード
from typing import Any
from strands.types.tools import ToolResult, ToolUse
import json
TOOL_SPEC = {
"name": "queries_decomposition",
"description": "ユーザーが提供する質問に対して、検索効率を高めるためのクエリ分解を行う。与えられる質問文に基づいて類義語や表記揺れを考慮し、多角的な視点からクエリを生成する。このクエリは、元の質問に関連する用語や同義語を含む検索用のクエリを生成する。これは検索範囲を広げることに役立つ。",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"query_decomposition": {
"type": "array",
"minItems": 1,
"maxItems": 2,
"items": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "複雑な質問をそれ単体で回答可能な粒度の文章に分解する。例) 質問「キャッシュレスの普及はどの程度進んでいるか教えてください。」を分解すると、「キャッシュレス決済の普及率と利用状況」「キャッシュレスの普及率の推移」「日本と世界各国のキャッシュレス化の進展度合い」などの文章に分解する。"
},
"relevance": {
"type": "number",
"description": "0から1の間で、クエリの関連度を付ける。0は関連度が低く、1は関連度が高い。0.01刻みで記述する。分解したクエリの関連度は、ユーザーが提供する質問との関連度を付ける。"
}
},
"required": ["query", "relevance"]
}
}
},
"required": ["query_decomposition"]
}
}
}
def queries_decomposition(tool: ToolUse, **kwargs: Any) -> ToolResult:
tool_use_id = tool["toolUseId"]
query_decomposition = tool["input"]["query_decomposition"]
# ツールの実行結果を出力
print("query_decomposition is started")
print("=" * 100)
print("query_decomposition: ", json.dumps(query_decomposition, indent=2, ensure_ascii=False))
return {
"toolUseId": tool_use_id,
"status": "success",
"content": [
{"text": "Query expansion completed successfully"},
{"json": {"query_expansion": query_decomposition}}
]
}
このツールでは、「片道200km程度の小旅行に便利な車を教えてください。家族で出かけます。高速道路を中心として一般道も多く走ります。」という質問を、以下のように分解します。一つ目のクエリはもとの質問と似ており、より簡潔にまとめています。二つ目のクエリはもとの質問とは異なる内容ですが、高速道路と一般道での走行性能について一般的な内容を示すものです。
[
{
"query": "片道200km程度の家族旅行に適した車種の特徴と選び方",
"relevance": 0.95
},
{
"query": "高速道路と一般道での快適な走行性能を持つ車の条件",
"relevance": 0.88
}
]
クエリ拡張ツール
クエリ拡張ツールの定義は以下のとおりです。queries_expansionという名前でツールを定義するので、関数名も同じ名前を持つqueries_expansion()を定義します。
クエリ拡張ツールの定義を行うサンプルコード
from typing import Any
from strands.types.tools import ToolResult, ToolUse
import json
TOOL_SPEC = {
"name": "queries_expansion",
"description": "ユーザーが提供する質問に対して、検索効率を高めるためのクエリ拡張を行う。与えられる質問文に基づいて類義語や日本語の表記揺れを考慮し、多角的な視点からクエリを生成する。このクエリは、元の質問に関連する用語や同義語を含む検索用のクエリを生成する。これは検索範囲を広げることに役立つ。",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"query_expansion": {
"type": "array",
"minItems": 2,
"maxItems": 4,
"items": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "多様な単語をクォートで囲み、それぞれカンマで区切って記述する。focusに示すそれぞれの観点に応じた用語を用いる。例) '小売り', 'コンビニ', 'スーパーマーケット', '家族', '年代別', 'キャッシュレス' "
},
"focus": {
"type": "string",
"description": "このクエリが注目する観点。",
"enum": ["技術的側面", "一般的説明", "比較分析", "経済的側面"]
}
},
"required": ["query", "focus"]
}
}
},
"required": ["query_expansion"]
}
}
}
def queries_expansion(tool: ToolUse, **kwargs: Any) -> ToolResult:
tool_use_id = tool["toolUseId"]
query_expansion = tool["input"]["query_expansion"]
# ツールの実行結果を出力
print("query_expansion is started")
print("=" * 100)
print("query_expansion: ", json.dumps(query_expansion, indent=2, ensure_ascii=False))
return {
"toolUseId": tool_use_id,
"status": "success",
"content": [
{"text": "Query expansion completed successfully"},
{"json": {"query_expansion": query_expansion}}
]
}
このツールでは、「片道200km程度の小旅行に便利な車を教えてください。家族で出かけます。高速道路を中心として一般道も多く走ります。」という質問を、以下のように拡張します。ツールで定義した観点(focus)に応じたクエリを生成しています。もとの質問になかった「SUV」「ミニバン」といった車種や「ハイブリッド」「ガソリン車」といった動力方式、「クルーズコントロール」といった運転支援に関するものなど多様なキーワードを含んでいます。
[
{
"query": "'ファミリーカー', '家族向け', 'SUV', 'ミニバン', 'コンパクトカー', '燃費', '乗り心地', '安全性', '荷室', 'ラゲッジスペース'",
"focus": "一般的説明"
},
{
"query": "'高速道路', '長距離運転', '走行安定性', 'クルーズコントロール', '静粛性', 'エンジン性能', 'ハイブリッド', 'ガソリン車'",
"focus": "技術的側面"
}
]
エージェントの作成
エージェントの作成は、BedrockModel()によるモデル呼び出しの初期化と、Agent()によるエージェントの作成のみと非常にシンプルです。Agent()の初期化時にツールを定義したPythonモジュールを自動的に呼び出すためにload_tools_from_directory=True
を指定します。また、ストリーミングによる出力を制御するために、callback_handler=None
を指定します。
BedrockModel()でstreaming=True
を指定していますが、ストリーミングはデフォルトで有効です。ここでは、明示的にパラメータを指定しました。
システムプロンプトでは、クエリ分解とクエリ拡張の両方のツールを使用するように指示しています。このような指示がない場合、エージェントはツールが必要と判断した場合のみツールを呼び出します。ツールの使用を任意とす場合は、指示の内容を調整します。
呼び出すLLMによってBedrockModel()で指定可能なパラメータが異なるため、ドキュメントを参考に必要な設定を行います。
エージェントの実行時に: Agent has reached an unrecoverable state due to max_tokens limit.
というエラーが発生した場合は、モデルの最大トークン数(max_tokens)を増やす必要があります。
https://strandsagents.com/latest/user-guide/concepts/agents/agent-loop/#maxtokensreachedexception
エージェントの実行を行うサンプルコード
from strands import Agent
from strands.models import BedrockModel
import asyncio
system_prompt = """
あなたは質問回答エージェントです。回答形式に従って回答してください。
- ユーザーの質問とその背景を理解するために、クエリを拡張と分解を行う便利なツールを使用する必要があります。
- クエリ拡張とクエリ分解を行った結果の情報のみを使用して、ユーザーの質問に答える必要があります。
- 拡張されたクエリの情報のみを使用して、ユーザーの質問に答える必要があります。
- ユーザーのクエリを自分で拡張する必要はありません。ツールがサポートします。
- クエリ拡張結果とクエリ分解結果を提供します。
- 「回答は次のとおりです:」「レポートは次のとおりです」「拡張されたクエリのキーワードに基づく回答は次のとおりです」「ユーザーの質問を理解するためにクエリの拡張と分解を行います。」などのフレーズは含めず、回答のみを提供してください。このようなフレーズは余計な出力トークンを消費し、ユーザーのコストを増加させます。
## 重要:説明文中心の回答スタイル
**流れのある文章による説明を最優先してください。**
情報を整理する際も、文章の中で自然に説明を組み立て、読み手が順序よく理解できるよう配慮してください。
## 回答形式
### 1. 構成(必須の3段階)
**概要(導入部分)**:
- 質問の核心を1-2段落で説明し、回答の方向性を示してください
- 読み手が全体像を把握できるよう、主要なポイントを文章で紹介してください
**詳細説明(本論部分)**:
- 豊富な文章による詳細な解説を行ってください(最低3-5段落)
- 各段落では一つの観点を深く掘り下げ、十分な説明を提供してください
- 段落間の関連性を明示し、論理的な流れを作ってください
**まとめ(結論部分)**:
- 重要なポイントを文章で整理し、全体を俯瞰した結論を述べてください
- 今後の展望や示唆があれば、文章で補足してください
### 2. 説明文重視の具体的指針
**専門用語の扱い**:
- 難しい用語は初出時に必ず文章内で説明してください(例:「機械学習とは、コンピューターがデータから自動的にパターンを学習する技術のことで...」)
- 略語は正式名称と併記し、その意味を文章で説明してください
**段階的説明**:
- 複雑な概念は段落を分けて、基礎から応用へと段階的に説明してください
- 各段落で前の内容を受けつつ、新しい情報を追加する構成にしてください
**具体例の活用**:
- 抽象的な概念には具体的な事例や比喩を文章で示してください
- 「例えば」「具体的には」「つまり」などの接続詞を使って説明を充実させてください
### 3. 表・リスト使用の制限
**基本方針**:
- 文章を基本とし、丁寧な説明を最優先してください。
- 表の前に2-3段落の詳細な説明を記述してください
### 4. 文章の質向上
**接続詞の活用**:「しかし」「一方で」「そのため」「このように」などを使い、文章間の関連性を明確にしてください
**段落構成**:各段落は5-8文で構成し、一つのテーマを十分に説明してください
**読み手への配慮**:「重要な点として」「注目すべきは」「特に理解しておきたいのは」などの表現で読み手の注意を引きつけてください
このスタイルに従い、文章の力で読み手に深い理解を提供してください。
"""
async def main():
model = BedrockModel(
temperature=1.0,
top_p=1.0,
max_tokens=2048,
model_id="global.anthropic.claude-sonnet-4-20250514-v1:0",
region_name="us-west-2",
streaming=True,
)
agent = Agent(
load_tools_from_directory=True,
system_prompt=system_prompt,
model=model,
callback_handler=None
)
response = agent.stream_async(user_message)
async for chunk in response:
if "data" in chunk:
# Strands splits the response into reasoningText and data.
print(chunk["data"], end="", flush=True)
if __name__ == "__main__":
user_message = "片道200km程度の小旅行に便利な車を教えてください。家族で出かけます。高速道路を中心として一般道も多く走ります。"
print("Agent is started")
print("User message: ", user_message)
print("=" * 100)
asyncio.run(main())
実行結果
エージェントの実行結果は以下のとおりです。クエリ分解とクエリ拡張の両方のツールを使用して、ユーザーの質問に対する回答を生成していることが読み取れます。このように、もとの質問にないキーワードを含むクエリを用いて検索を行うことで、より多面的な情報を取得できます。また、クエリ分解により、質問をより具体的なものにし、回答の質を高めることが期待できます。
ツール定義の解釈や生成するクエリの内容は、LLMによって特徴が異なるようです。AnthropicのClaude 4 Sonnetは、ツール定義を正しく解釈し生成するクエリの形式も定義どおりのものを生成できました。gpt-oss-120Bは、まれにクエリの生成のみで処理が終わってしまうことがありました。
性能面では、Claude 4 Sonnetがおよそ30秒かかるのにたいして、gpt-oss-120Bはおよそ15秒で処理が終わりました。回答内容についは、Claude 4 Sonnetのほうがより詳細な内容を生成するように感じました。この点は、モデルごとにプロンプトを調整することで改善できるかもしれません。
❯ uv run python ./agent.py
Agent is started
User message: 片道200km程度の小旅行に便利な車を教えてください。家族で出かけます。高速道路を中心として一般道も多く走ります。
====================================================================================================
query_expansion is started
====================================================================================================
query_expansion: [
{
"query": "'ファミリーカー', '家族向け', 'SUV', 'ミニバン', 'コンパクトカー', '燃費', '乗り心地', '安全性', '荷室', 'ラゲッジスペース'",
"focus": "一般的説明"
},
{
"query": "'高速道路', '長距離運転', '走行安定性', 'クルーズコントロール', '静粛性', 'エンジン性能', 'ハイブリッド', 'ガソリン車'",
"focus": "技術的側面"
},
{
"query": "'200km', '小旅行', '日帰り', '1泊旅行', '家族旅行', '観光', 'ドライブ', '旅行用品', '快適性'",
"focus": "比較分析"
},
{
"query": "'車種選び', '購入費用', '維持費', '燃料費', 'コストパフォーマンス', '中古車', '新車', 'リース'",
"focus": "経済的側面"
}
]
query_decomposition is started
====================================================================================================
query_decomposition: [
{
"query": "片道200km程度の家族旅行に適した車種の特徴と選び方",
"relevance": 0.95
},
{
"query": "高速道路と一般道での快適な走行性能を持つ車の条件",
"relevance": 0.88
}
]
## 概要
片道200kmの家族での小旅行には、快適性と実用性を兼ね備えた車選びが重要になります。この距離は約2-3時間の運転時間となるため、家族全員が疲れにくく、高速道路での安定性と一般道での取り回しの良さの両方を持つ車種が理想的です。燃費効率、荷物の積載能力、そして長時間の運転でも疲労を軽減できる快適装備を備えた車が、家族旅行を成功させる鍵となるでしょう。
## 詳細説明
### ミニバン系統の選択肢
家族での小旅行において最も実用的なのがミニバンです。トヨタのヴォクシーやノア、ホンダのステップワゴンなどは、7-8人乗車可能でありながら、家族4-5人での利用時には荷室スペースを最大限活用できます。これらの車種は、旅行用品やお土産を余裕で積載でき、さらに後席の快適性も優れています。特に注目すべきは、スライドドアの利便性で、狭い駐車場でも乗降が容易であり、小さなお子様がいる家族には非常に重宝します。また、最新のミニバンは燃費性能も向上しており、ハイブリッドモデルでは15-20km/L程度の燃費を実現しています。
### SUVという選択肢の魅力
近年人気が高まっているSUVも、家族旅行には優れた選択肢となります。トヨタのハリアーやマツダのCX-5、ホンダのヴェゼルなどは、高い着座位置により視界が良好で、長距離運転時の疲労軽減に効果的です。SUVの特徴として、高速道路での走行安定性が挙げられ、風の影響を受けにくく、直進安定性に優れています。また、一般道での段差や悪路にも対応でき、旅行先での多様な道路状況に柔軟に対応できる点も魅力です。荷室の形状も四角く使いやすいため、旅行用品の整理整頓がしやすいという実用的なメリットもあります。
### コンパクトカーの経済性と機能性
家族構成が3-4人程度であれば、コンパクトカーも十分に選択肢となります。トヨタのアクアやホンダのフィット、日産のノートe-POWERなどは、優れた燃費性能を持ちながら、意外に広い室内空間を確保しています。これらの車種の最大の魅力は経済性で、燃料費を大幅に抑えながら快適な旅行が可能です。特にハイブリッド車やe-POWERシステムを搭載したモデルでは、25-30km/L以上の燃費を実現し、200km往復でも燃料費は非常に経済的です。また、取り回しの良さから、観光地での駐車や狭い道路での運転も楽に行えます。
### 高速道路走行における重要な機能
片道200kmの旅行では高速道路の利用が中心となるため、高速走行に適した機能を持つ車を選ぶことが重要です。クルーズコントロール機能は長距離運転の疲労軽減に大きく貢献し、特にアダプティブクルーズコントロール(ACC)を搭載した車種では、前車との車間距離を自動調整してくれるため、運転者の負担が大幅に軽減されます。また、車線維持支援システム(LKA)や車線逸脱警報(LDW)などの安全装備も、長時間運転での安全性向上に欠かせません。静粛性も重要な要素で、エンジン音や風切り音が少ない車種を選ぶことで、家族との会話や音楽鑑賞を楽しみながらの快適なドライブが実現できます。
### 燃費と維持費の考慮
200km程度の距離を定期的に走行する場合、燃費性能は運営コストに大きく影響します。ハイブリッド車は初期投資は高めですが、長期的には燃料費の節約効果が期待できます。一方、ガソリン車でも最新の直噴エンジンや CVT(無段変速機)を搭載したモデルでは、従来車種と比較して大幅な燃費向上が図られています。また、車両の維持費として、定期点検費用や消耗品の交換費用も考慮する必要があります。人気車種は部品供給が安定しており、メンテナンス費用も比較的抑えられる傾向にあります。
## まとめ
片道200kmの家族旅行に最適な車選びでは、快適性、経済性、安全性のバランスが重要です。ミニバンは最大の積載能力と乗車定員を提供し、大家族や荷物の多い旅行に最適です。SUVは走行性能と視界の良さで安全で快適な運転を実現し、コンパクトカーは優れた燃費性能で経済的な旅行を可能にします。
どの車種を選ぶにしても、高速道路での安定性、静粛性、そして運転支援システムの充実度を重視することで、家族全員が楽しめる小旅行が実現できるでしょう。最終的には、家族構成、予算、そして個人の好みを総合的に考慮して、最適な一台を選択することが大切です。
まとめ
今回の記事では、Strands Agents SDK を使って「クエリ分解」と「クエリ拡張」を行うツールを定義し、生成 AI の回答精度向上を図る方法を紹介しました。2 つのツールやエージェントの実装には、以下のような特徴があります。
- ツールの定義やエージェントの実装は非常にシンプル
- クエリ拡張ツールにより、多様なキーワードを生成できる
- クエリ分解ツールにより、別の観点からのクエリを生成できる
- ツールを活用することで、より網羅的な検索が可能になり、回答の質を高めることが期待できる
このツールを RAG の検索に利用することでより網羅的な検索が可能になり、キーワード検索とセマンティック検索を行うハイブリッド検索の精度を向上させることが期待できます。