はじめに
昨今、エージェントやチャットボット等のAIアプリを作るハードルはグッと下がりました。社内向けにアプリを展開している方も多いのではないかと思います。
しかし、シンプルにLLMにクエリを投げるだけだと、業務と無関係な会話も出来てしまいます。コスト・ガバナンス的にも、私的利用の蔓延は避けたいところです。
そこで今回は、Bedrock Guardrailsを活用し、業務と無関係な会話をブロックしてみます。
Bedrock Guardrailsとは
生成AIアプリを保護するための様々な機能を提供する、AWSマネージドサービスです。主に下表のような機能があります。
| 機能 | 概要 |
|---|---|
| コンテンツフィルター | 「差別」「暴力」などの基準に従ったコンテンツフィルタリングを行う |
| 拒否トピック | 好ましくないトピックの会話が行われないようにする |
| ワードフィルター | 特定の単語・フレーズによるフィルタリングを行う |
| 機密情報フィルター | 住所や氏名といった個人情報をブロックまたはマスクする |
詳細は以下の公式ドキュメントを参照ください。
https://aws.amazon.com/jp/bedrock/guardrails/
https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/guardrails.html
今回は「拒否トピック」を使って、業務と無関係な会話をブロックしていきます。
拒否トピックの設定
拒否トピックでは、トピックの定義と、サンプルフレーズを設定します。サンプルフレーズは、1トピックあたり5個まで設定可能です。また、日本語をサポートするには、 Denied topics tier を Standard に設定します。
Denied topics tier を Standard にするための前提条件として、Guardrailsの Cross-Region inference を有効にする必要があります。
今回は、業務アプリの具体的な業務内容を定義していないため、よくある一般的な会話の話題として、以下のトピックを設定しました。
| カテゴリ | 定義 |
|---|---|
| 日常会話・雑談 | 天気、挨拶、世間話、日常の出来事、季節の話題 |
| エンターテインメント | 映画、音楽、テレビ番組、ゲーム、芸能人、アニメ、漫画、動画配信サービスなど、娯楽コンテンツに関する話題 |
| スポーツ | 野球、サッカー、テニス、オリンピックなど、各種スポーツ競技、選手、試合結果、大会に関する話題 |
| 政治 | 政党、選挙、政策、政治家、国会審議、外交、政治制度など、国内外の政治に関する話題 |
| 宗教・信仰 | 仏教、キリスト教、イスラム教などの宗教、信仰、宗教的行事、教義、神社仏閣、祈りに関する話題 |
| 個人相談・悩み相談 | 人間関係、恋愛、家族問題、人生相談、職場の悩み、メンタルヘルスなど、個人的な悩みや相談に関する話題 |
| ライフスタイル | 料理、ファッション、旅行、趣味、インテリア、ガーデニング、DIYなど、日常生活の楽しみ方や暮らしに関する話題 |
| 健康・医療 | 病気、症状、治療法、ダイエット、健康法、薬、予防接種、メンタルヘルスケアなど、健康や医療に関する話題 |
| 金融・投資 | 株式、仮想通貨、資産運用、保険、住宅ローン、年金、節約術など、お金や金融商品に関する話題 |
| 教育・学習 | 受験、資格試験、学校教育、学習方法、塾、プログラミング学習、語学学習など、教育や学びに関する話題 |
| 社会問題・時事 | 少子高齢化、環境問題、ニュース、事件、国際情勢、格差問題など、社会的な議論や時事的な話題 |
| 哲学・思想 | 倫理、道徳、人生観、哲学的議論、思想家の考え方、生きる意味、価値観に関する話題占い・オカルト 星座占い、タロット、風水、超常現象、スピリチュアル、心霊、UFO、都市伝説に関する話題 |
| ペット・動物 | 犬、猫などのペットの飼育方法、動物の生態、動物園、野生動物、動物の種類に関する話題 |
| ギャンブル | 競馬、パチンコ、カジノ、宝くじ、競輪、競艇、スポーツベッティングなど、賭け事や運試しに関する話題 |
| 自然科学 | 物理学、化学、生物学、天文学、地球科学、数学、宇宙、気象現象など、自然界の法則や科学に関する話題 |
| 芸術 | 絵画、彫刻、美術館、演劇、ダンス、写真、書道、陶芸など、芸術作品や創作活動に関する話題 |
| 歴史・地理 | 歴史的事件、歴史上の人物、世界遺産、各国の地理、文化遺産、古代文明、戦争の歴史に関する話題 |
各カテゴリ毎に設定したサンプルプロンプトについては、以下のjsonを参照ください。
カテゴリ毎のサンプルプロンプト
{
"categories": [
{
"name": "日常会話・雑談",
"definition": "天気、挨拶、世間話、日常の出来事、季節の話題など、業務に直接関係しない一般的な会話や雑談に関する話題",
"sample_phrases": [
"今日の天気はどうですか",
"最近調子はどうですか",
"週末は何をしましたか",
"おすすめのランチのお店を教えて",
"今日は暑いですね"
]
},
{
"name": "エンターテインメント",
"definition": "映画、音楽、テレビ番組、ゲーム、芸能人、アニメ、漫画、動画配信サービスなど、娯楽コンテンツに関する話題",
"sample_phrases": [
"最近おすすめの映画はありますか",
"今期の人気アニメを教えてください",
"あのアーティストの新曲はどうですか",
"面白いゲームを紹介してください",
"昨日のドラマの感想を聞かせて"
]
},
{
"name": "スポーツ",
"definition": "野球、サッカー、テニス、バスケットボール、オリンピックなど、各種スポーツ競技、選手、試合結果、大会に関する話題",
"sample_phrases": [
"昨日のプロ野球の試合結果を教えて",
"サッカーワールドカップの優勝候補はどこですか",
"大谷翔平選手の最新成績はどうですか",
"テニスの全豪オープンの結果を教えて",
"箱根駅伝の見どころを教えてください"
]
},
{
"name": "政治",
"definition": "政党、選挙、政策、政治家、国会審議、外交、政治制度など、国内外の政治に関する話題",
"sample_phrases": [
"次の選挙はいつですか",
"今の首相の支持率はどのくらいですか",
"消費税についてどう思いますか",
"与党と野党の違いを教えてください",
"最近の国会で議論されていることは何ですか"
]
},
{
"name": "宗教・信仰",
"definition": "仏教、キリスト教、イスラム教などの宗教、信仰、宗教的行事、教義、神社仏閣、祈りに関する話題",
"sample_phrases": [
"お盆の意味を教えてください",
"クリスマスの由来は何ですか",
"神社とお寺の違いは何ですか",
"世界三大宗教について教えてください",
"お祈りの作法を教えてください"
]
},
{
"name": "個人相談・悩み相談",
"definition": "人間関係、恋愛、家族問題、人生相談、職場の悩み、メンタルヘルスなど、個人的な悩みや相談に関する話題",
"sample_phrases": [
"友人との関係がうまくいかないのですが",
"恋人との喧嘩を仲直りする方法を教えて",
"将来が不安で眠れません",
"職場の人間関係で悩んでいます",
"自分に自信が持てないのですがどうすればいいですか"
]
},
{
"name": "ライフスタイル",
"definition": "料理、ファッション、旅行、趣味、インテリア、ガーデニング、DIYなど、日常生活の楽しみ方や暮らしに関する話題",
"sample_phrases": [
"簡単に作れる夕食のレシピを教えて",
"今年の秋のファッショントレンドは何ですか",
"おすすめの国内旅行先を教えてください",
"部屋のインテリアをおしゃれにするコツは",
"初心者におすすめの趣味はありますか"
]
},
{
"name": "健康・医療",
"definition": "病気、症状、治療法、ダイエット、健康法、薬、予防接種、メンタルヘルスケアなど、健康や医療に関する話題",
"sample_phrases": [
"頭痛が続くのですが原因は何ですか",
"効果的なダイエット方法を教えてください",
"インフルエンザの予防法を教えて",
"肩こりを解消するストレッチはありますか",
"花粉症の対策を教えてください"
]
},
{
"name": "金融・投資",
"definition": "株式、仮想通貨、資産運用、保険、住宅ローン、年金、節約術など、お金や金融商品に関する話題",
"sample_phrases": [
"初心者におすすめの投資方法はありますか",
"NISAとiDeCoの違いを教えてください",
"仮想通貨は今から始めても遅くないですか",
"生命保険の選び方を教えてください",
"住宅ローンの金利はどのくらいですか"
]
},
{
"name": "教育・学習",
"definition": "受験、資格試験、学校教育、学習方法、塾、プログラミング学習、語学学習など、教育や学びに関する話題",
"sample_phrases": [
"大学受験の勉強法を教えてください",
"TOEICのスコアを上げるコツはありますか",
"子供の習い事は何がおすすめですか",
"効率的な暗記方法を教えてください",
"プログラミングの独学方法を教えて"
]
},
{
"name": "社会問題・時事",
"definition": "少子高齢化、環境問題、ニュース、事件、国際情勢、格差問題など、社会的な議論や時事的な話題",
"sample_phrases": [
"少子高齢化の対策はどうなっていますか",
"地球温暖化の影響について教えてください",
"最近の大きなニュースは何ですか",
"日本の人口減少問題についてどう思いますか",
"SDGsとは何ですか"
]
},
{
"name": "哲学・思想",
"definition": "倫理、道徳、人生観、哲学的議論、思想家の考え方、生きる意味、価値観に関する話題",
"sample_phrases": [
"人生の意味とは何だと思いますか",
"幸せとは何ですか",
"正義とは何かについて教えてください",
"有名な哲学者の考え方を教えて",
"自由意志は存在すると思いますか"
]
},
{
"name": "占い・オカルト",
"definition": "星座占い、タロット、風水、超常現象、スピリチュアル、心霊、UFO、都市伝説に関する話題",
"sample_phrases": [
"今日の星座占いを教えてください",
"風水で運気を上げる方法はありますか",
"タロットカードの意味を教えて",
"心霊スポットについて教えてください",
"パワースポットのおすすめはどこですか"
]
},
{
"name": "ペット・動物",
"definition": "犬、猫などのペットの飼育方法、動物の生態、動物園、野生動物、動物の種類に関する話題",
"sample_phrases": [
"犬のしつけ方法を教えてください",
"猫が喜ぶおもちゃはありますか",
"初心者でも飼いやすいペットは何ですか",
"動物園のおすすめスポットを教えて",
"金魚の飼い方を教えてください"
]
},
{
"name": "ギャンブル",
"definition": "競馬、パチンコ、カジノ、宝くじ、競輪、競艇、スポーツベッティングなど、賭け事や運試しに関する話題",
"sample_phrases": [
"競馬の予想方法を教えてください",
"宝くじの当選確率はどのくらいですか",
"パチンコで勝つコツはありますか",
"カジノで人気のゲームは何ですか",
"競艇の楽しみ方を教えてください"
]
},
{
"name": "自然科学",
"definition": "物理学、化学、生物学、天文学、地球科学、数学、宇宙、気象現象など、自然界の法則や科学に関する話題",
"sample_phrases": [
"ブラックホールとは何ですか",
"なぜ空は青いのですか",
"DNAとRNAの違いを教えてください",
"地震が起きる仕組みを教えて",
"光の速さはどのくらいですか"
]
},
{
"name": "芸術",
"definition": "絵画、彫刻、美術館、演劇、ダンス、写真、書道、陶芸など、芸術作品や創作活動に関する話題",
"sample_phrases": [
"モナリザの作者は誰ですか",
"おすすめの美術館を教えてください",
"写真撮影のコツを教えてください",
"有名なバレエ作品を教えて",
"陶芸の始め方を教えてください"
]
},
{
"name": "歴史・地理",
"definition": "歴史的事件、歴史上の人物、世界遺産、各国の地理、文化遺産、古代文明、戦争の歴史に関する話題",
"sample_phrases": [
"織田信長について教えてください",
"世界遺産のおすすめを教えて",
"第二次世界大戦の原因は何ですか",
"エジプトのピラミッドについて教えて",
"日本の都道府県で一番面積が大きいのはどこですか"
]
}
]
}
テスト
拒否トピックを設定したら、期待通りにブロックしてくれるかテストします。
テスト方法
(参考)マネジメントコンソールを使った方法
Bedrock Guardrailsでは、マネジメントコンソール上でテストを実施することが可能です。
ブロックされるかどうかと、もしブロックされた場合は、どの設定で検知してブロックしたか確認することが出来ます。手軽に確認するには最適の方法です。
ただし、「業務と無関係な会話」という幅広いテーマで検証するには大量のプロンプトを試す必要があるため、この方法では時間がかかり過ぎてしまいます。そのため、今回は別の方法をとることにしました。
今回採用した方法
今回は、以下の方法を採用しました。
- 生成AIで、各カテゴリ毎に100個、テスト用のプロンプトを作成(合計1,800個)
- 作成したプロンプトをApplyGuardrail APIでチェックし、集計結果を表示するスクリプトを作成・実行
ApplyGuardrail APIについて
ApplyGuardrail APIでは、AIアプリの基盤モデルを呼び出すことなく、Guardrailsでプロンプトの検査を行うことが出来ます。そのため、マネジメントコンソールのテストと比較し、迅速にテストを行うことが出来ます。
(マネジメントコンソールのテストでは、ブロックされなかった場合は基盤モデルが回答を作成するため、その分の待ち時間が発生します。)
今回の実測値では、1プロンプトあたり約0.5秒で評価することが出来ました。
ApplyGuardrail APIの詳細については以下の公式ドキュメントを参照ください。
https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/guardrails-use-independent-api.html
テスト用プロンプトは、下図のように category と prompt の項目を持つCSVファイル形式としました。
全量は以下リンクを参照ください。
https://github.com/Y-Yamashita-83/bedrock-guardrails-test/blob/main/test_prompts.csv
テスト用プロンプトをテストし集計結果を表示するスクリプトについては、以下を参照ください。
スクリプト
#!/usr/bin/env python3
"""
Amazon Bedrock Guardrails テストスクリプト
ApplyGuardrail APIを使用してテストプロンプトを評価し、
カテゴリごとのブロック精度を検証する。
"""
import argparse
import csv
import logging
import sys
import boto3
from botocore.config import Config
from botocore.exceptions import ClientError
# ロギング設定
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger(__name__)
def parse_args(args=None):
"""コマンドライン引数をパースする。"""
parser = argparse.ArgumentParser(
description="Amazon Bedrock Guardrails テストスクリプト"
)
parser.add_argument(
"--guardrail-id",
required=True,
help="GuardrailのID",
)
parser.add_argument(
"--guardrail-version",
required=True,
help="Guardrailのバージョン",
)
parser.add_argument(
"--input-csv",
required=True,
help="テストプロンプトCSVのパス",
)
parser.add_argument(
"--output-csv",
default="test_results.csv",
help="結果出力CSVのパス(デフォルト: test_results.csv)",
)
parser.add_argument(
"--region",
default="us-east-1",
help="AWSリージョン(デフォルト: us-east-1)",
)
return parser.parse_args(args)
def create_bedrock_client(region):
"""アダプティブリトライ設定付きのbedrock-runtimeクライアントを作成する。"""
config = Config(
retries={
"max_attempts": 5,
"mode": "adaptive",
}
)
return boto3.client("bedrock-runtime", region_name=region, config=config)
def read_input_csv(input_csv_path):
"""入力CSVファイルからテストプロンプトを読み込む。
Returns:
list[dict]: categoryとpromptを持つ辞書のリスト
"""
prompts = []
with open(input_csv_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
if reader.fieldnames is None or "category" not in reader.fieldnames or "prompt" not in reader.fieldnames:
logger.error("入力CSVに必要なカラム(category, prompt)がありません")
sys.exit(1)
for row in reader:
prompts.append({"category": row["category"], "prompt": row["prompt"]})
return prompts
def call_apply_guardrail(client, guardrail_id, guardrail_version, prompt_text):
"""ApplyGuardrail APIを呼び出し、レスポンスを解析する。
Returns:
dict: action, detected_topics, detected_actions を含む結果辞書
"""
response = client.apply_guardrail(
guardrailIdentifier=guardrail_id,
guardrailVersion=guardrail_version,
source="INPUT",
content=[{"text": {"text": prompt_text}}],
)
action = response["action"]
topics = []
for assessment in response.get("assessments", []):
topic_policy = assessment.get("topicPolicy", {})
for topic in topic_policy.get("topics", []):
topics.append(
{"name": topic["name"], "type": topic["type"], "action": topic["action"]}
)
detected_topic_names = ";".join(t["name"] for t in topics)
detected_actions = ";".join(t["action"] for t in topics)
return {
"action": action,
"detected_topics": detected_topic_names,
"detected_actions": detected_actions,
}
def run_tests(client, guardrail_id, guardrail_version, prompts):
"""全テストプロンプトに対してApplyGuardrail APIを呼び出し、結果を収集する。
ResourceNotFoundException発生時はスクリプトを終了する。
その他のAPIエラーはログに記録し、残りのテストを継続する。
Returns:
tuple: (results list, error_count)
"""
results = []
error_count = 0
total = len(prompts)
for i, prompt_data in enumerate(prompts, start=1):
category = prompt_data["category"]
prompt_text = prompt_data["prompt"]
try:
result = call_apply_guardrail(
client, guardrail_id, guardrail_version, prompt_text
)
results.append(
{
"category": category,
"prompt": prompt_text,
"action": result["action"],
"detected_topics": result["detected_topics"],
"detected_actions": result["detected_actions"],
}
)
logger.info(
"[%d/%d] %s | %s | action=%s",
i,
total,
category,
prompt_text[:30],
result["action"],
)
except ClientError as e:
error_code = e.response["Error"]["Code"]
if error_code == "ResourceNotFoundException":
logger.error(
"Guardrailが見つかりません(ID=%s, Version=%s)。スクリプトを終了します。",
guardrail_id,
guardrail_version,
)
sys.exit(1)
# その他のAPIエラー: ログに記録して継続
logger.warning(
"[%d/%d] APIエラー (%s): %s | %s",
i,
total,
error_code,
category,
prompt_text[:30],
)
error_count += 1
results.append(
{
"category": category,
"prompt": prompt_text,
"action": "ERROR",
"detected_topics": "",
"detected_actions": "",
}
)
return results, error_count
def write_results_csv(results, output_csv_path):
"""テスト結果をCSVファイルに出力する。
Args:
results: category, prompt, action, detected_topics, detected_actions を持つ辞書のリスト
output_csv_path: 出力CSVファイルのパス
"""
fieldnames = ["category", "prompt", "action", "detected_topics", "detected_actions"]
with open(output_csv_path, "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)
def print_summary(results, error_count, guardrail_id, guardrail_version):
"""カテゴリ別ブロック率と全体統計をコンソールに表示する。
Args:
results: category, prompt, action を持つ辞書のリスト
error_count: エラー数
guardrail_id: Guardrail ID
guardrail_version: Guardrail バージョン
"""
from collections import OrderedDict
from datetime import datetime
# カテゴリ別集計(出現順を保持)
category_stats = OrderedDict()
for r in results:
cat = r["category"]
if cat not in category_stats:
category_stats[cat] = {"total": 0, "blocked": 0}
category_stats[cat]["total"] += 1
if r["action"] == "GUARDRAIL_INTERVENED":
category_stats[cat]["blocked"] += 1
# 全体集計
total_tests = len(results)
total_blocked = sum(s["blocked"] for s in category_stats.values())
overall_rate = (total_blocked / total_tests * 100) if total_tests > 0 else 0.0
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print("========================================")
print("Guardrail テスト結果サマリー")
print("========================================")
print(f"Guardrail ID: {guardrail_id}")
print(f"Version: {guardrail_version}")
print(f"テスト実行日時: {now}")
print()
print("カテゴリ別ブロック率:")
# カテゴリ名の最大幅を計算して整列
max_name_len = max((len(cat) for cat in category_stats), default=0)
for cat, stats in category_stats.items():
rate = (stats["blocked"] / stats["total"] * 100) if stats["total"] > 0 else 0.0
count_str = f"{stats['blocked']}/{stats['total']}"
print(f" {cat}:{' ' * (max_name_len - len(cat) + 2)}{count_str} ({rate:.1f}%)")
print()
print("全体:")
print(f" 総テスト数: {total_tests}")
print(f" ブロック数: {total_blocked}")
print(f" 全体ブロック率: {overall_rate:.1f}%")
print()
print(f"エラー数: {error_count}")
print("========================================")
def main():
args = parse_args()
logger.info("Guardrail ID: %s", args.guardrail_id)
logger.info("Guardrail Version: %s", args.guardrail_version)
logger.info("入力CSV: %s", args.input_csv)
logger.info("出力CSV: %s", args.output_csv)
logger.info("リージョン: %s", args.region)
# 入力CSV読み込み
try:
prompts = read_input_csv(args.input_csv)
except FileNotFoundError:
logger.error("入力CSVファイルが見つかりません: %s", args.input_csv)
sys.exit(1)
except Exception as e:
logger.error("入力CSVの読み込みに失敗しました: %s", e)
sys.exit(1)
logger.info("テストプロンプト数: %d", len(prompts))
# boto3クライアント初期化
client = create_bedrock_client(args.region)
# テスト実行
results, error_count = run_tests(
client, args.guardrail_id, args.guardrail_version, prompts
)
# テスト結果CSVの出力
try:
write_results_csv(results, args.output_csv)
logger.info("テスト結果を出力しました: %s", args.output_csv)
except Exception as e:
logger.error("結果CSVの書き込みに失敗しました: %s", e)
sys.exit(1)
# コンソールサマリー表示
print_summary(results, error_count, args.guardrail_id, args.guardrail_version)
if __name__ == "__main__":
main()
テスト実施結果
それではテストを実施します。スクリプトを実行すると、テストプロンプト毎にブロックの有無を表示してくれます。action=GUARDRAIL_INTERVENED であればブロック成功。action=NONE の場合はブロック失敗です。(下図参照)
1,800個のプロンプトを全て評価するのに、おおよそ15分程度かかりました。
テスト結果の全量については、以下リンクのCSVファイルを参照ください。
こちらでは、どの拒否トピックでブロックしたかまで記録されています。複数のトピックでブロックしているケースもありました。
https://github.com/Y-Yamashita-83/bedrock-guardrails-test/blob/main/test_results(sample).csv
結果として、カテゴリ別で、以下のブロック率を記録しました。
| カテゴリ | ブロック率 |
|---|---|
| 日常会話・雑談 | 100/100 (100.0%) |
| エンターテインメント | 97/100 (97.0%) |
| スポーツ | 81/100 (81.0%) |
| 政治 | 90/100 (90.0%) |
| 宗教・信仰 | 96/100 (96.0%) |
| 個人相談・悩み相談 | 98/100 (98.0%) |
| ライフスタイル | 96/100 (96.0%) |
| 健康・医療 | 98/100 (98.0%) |
| 金融・投資 | 94/100 (94.0%) |
| 教育・学習 | 90/100 (90.0%) |
| 社会問題・時事 | 75/100 (75.0%) |
| 哲学・思想 | 100/100 (100.0%) |
| 占い・オカルト | 92/100 (92.0%) |
| ペット・動物 | 97/100 (97.0%) |
| ギャンブル | 97/100 (97.0%) |
| 自然科学 | 95/100 (95.0%) |
| 芸術 | 88/100 (88.0%) |
| 歴史・地理 | 98/100 (98.0%) |
| 全体 | 1682/1800 (93.4%) |
全体として90%以上のブロック率になりました。スポーツのブロック率が伸びなかったのは、少し意外でした。
課題
というわけで、それなりの精度でブロック出来た感じがします。ただ、テスト自体の品質について、まだ改善の余地がありそうです。
例えば、以下のような点が気になります。
- テスト用プロンプトの内容に十分なバラつきがあるか?似たようなプロンプトばかりになっていないか?
- 一つ一つのプロンプトが短文だが、もっと長い文章も含めた方が良いのでは?
- 「カテゴリ毎に100個」というプロンプト数は十分か?
- 過検知(偽陽性)に関するテストも実施するべきでは?
AIアプリを本格的に導入する場合は、更にテストをブラッシュアップしていきたいと思います。
おまけ
今回のテストのために作ったスクリプトや、テスト結果のサンプルを、以下に格納してあります。
興味のある方は是非お試しください。
https://github.com/Y-Yamashita-83/bedrock-guardrails-test/tree/main
本記事は以上となります。何か少しでも参考になることがあれば幸いです。



