はじめに
「APIキーを直接コードに書くわけにいかないが、生成AIは使いたい」。先月、東京の物流系企業でPoCを進めていたとき、情シスの方からそう言われた。心情としてはよくわかる。エンドユーザー企業が外部LLMサービスのAPIキーを管理するのは、運用上もコンプライアンス上も負担が大きい。
そこで採用したのが、AWS Bedrock 経由でClaudeを呼び出す構成だ。AnthropicがAWS上での提供を強化していくという発表もあり、選択肢として現実味が増している(参考: Claude Platform on AWS)。本稿はその時のメモを整理したものだ。題材は 社内メールを3カテゴリに分類する最小実装。請求関連・サポート・その他に自動振り分け、というシンプルなユースケース。

前提と環境
Python 3.11
boto3 1.34+
AWS アカウント(東京リージョン ap-northeast-1)
Bedrock のClaudeモデル利用申請が承認済みであること
東京リージョンを選んだ理由は単純で、データの所在を国内に閉じたいというリクエストがあったから。PoC段階でも先方が真っ先に気にするポイントだった。
実装ステップ
1. boto3でBedrockクライアントを作る
import boto3
import json
client = boto3.client(
"bedrock-runtime",
region_name="ap-northeast-1",
)
ハマりポイント: bedrock と bedrock-runtime はサービス名が違う。前者はモデル一覧やプロビジョニング用、推論呼び出しは後者を使う。私はここで30分ハマった。あとからAWSのドキュメントを読み返したら太字で書いてあった。読まない人間が悪い。
2. プロンプトを作る
SYSTEM = """あなたは社内メールの分類アシスタントだ。
次のメール本文を、以下のいずれかに分類してJSONで返せ。
カテゴリ:
billing: 請求・支払い・契約金額に関するもの
support: 製品の使い方・障害・不具合に関するもの
other: 上記以外
出力フォーマット:
{"category": "billing" | "support" | "other", "reason": "30文字以内の理由"}
"""
def build_messages(body_text: str) -> list:
return [
{"role": "user", "content": body_text.strip()},
]
JSON で返させる時の鉄則は「分岐数を少なくする」こと。3カテゴリならほぼ外さないが、10カテゴリ超えると分類ミスが顕在化してくる。実務ではまず3つから始め、後で増やすほうがいい。
3. invoke_modelを呼ぶ
def classify(body_text: str) -> dict:
payload = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 200,
"system": SYSTEM,
"messages": build_messages(body_text),
"temperature": 0,
}
resp = client.invoke_model(
modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
body=json.dumps(payload),
)
raw = json.loads(resp["body"].read())
text = raw["content"][0]["text"]
return json.loads(text)
temperature=0 は分類タスクの基本だ。出力をブレさせる必要がない。地味だが anthropic_version を入れ忘れると 400 で蹴られる。Bedrock 経由特有の落とし穴で、ここも私は一度踏んだ。
動作確認
if name == "main":
sample = "先月分の請求書がまだ届いていません。確認をお願いします。"
print(classify(sample))
実行結果:
{'category': 'billing', 'reason': '請求書未着の確認依頼'}
30件のサンプルで試したところ、誤分類は1件だった。「サポート部門に支払い遅延の件を伝えたい」というメールが support に振られたケースだ。文字通りには正しいが、業務フロー上は billing に流したい。プロンプトに「支払い・契約に関するキーワードは billing 優先」と1行足したら直った。
30件で1件、3.3%。小さく見えるが、月1万件なら330件のミスルーティングになる。こういう微妙な揺らぎを潰すのが、PoCの8割を占める作業だと改めて感じた。
応用
分類結果をAWS Lambda経由でSlackやTeamsに通知する
分類スコアが低いものだけ人手レビューに回すフォールバックを組む
同じ仕組みでFAQ自動振り分け、製造現場の不具合報告分類などにも展開可能
東京リージョンに加えて、災害対策で大阪 or 米国リージョンへのフェイルオーバーを設計する
まとめ
Bedrock 経由は、APIキー管理の負担をAWSのIAMに寄せられるのが大きい。コードの行数はほぼ変わらないが、本番運用の文脈ではこの差が効いてくる。正直なところ、最初は「OpenAIを直で叩くほうが楽じゃないか」と思っていた。実際にエンタープライズ環境に持ち込むと、IAM・VPC・監査ログという既存のAWS資産に乗せられる強みのほうが、ずっと大きかった。次は同じ構成でClaudeの最新モデルが東京リージョンに来たときの latency 比較を書こうと思う。
筆者は 5years+ で韓国・日本企業向けにAI/LLMの業務応用を担当している。