はじめに
みなさんこんにちは!!
今年からエンジニアになり、AWSの勉強を始めました!
AWS Certified Cloud Practitionerは取得できたのですが、時間が経つと知識が離れていく...
弊社では毎日お昼にAWSの問題を解いているのですが、新人の私からすると少し難しい!!!
ということで、さくらのAI Engineを使ってガチャガチャ感覚でAWSの単語を学ぼうと思いました!
そう思って作ったのが、今回の「AWSキーワードガチャ」です。100行くらいのPythonコードで動くものができたので、同じく初心者の方の参考になれば嬉しいです。
作ったもの
ターミナルで実行すると、
- AWSのキーワードをガチャで1つ抽選
- そのキーワードのミニ解説をAIが書いてくれる
- 解説した内容から4択問題が出る
- A〜Dで答えると正解判定&解説
という流れで、AWSを1テーマずつ勉強できるCLIツールです。
問題は毎回AIが作るので、同じ問題が二度と出てきません。過去問集と違って無限に遊べます。
実行イメージ
🎰 今日のAWSキーワードガチャを回した! ガラガラ…
【キーワード】 セキュリティグループとネットワークACLの違い
【難易度】 ★☆☆(ビギナー)
🤖 さくらのAIが教材を作成中…
📖 まずはミニ解説:「セキュリティグループとネットワークACLの違い」って何?
Security Groupはインスタンス単位で適用されるステートフルな仮想ファイアウォールです。インバウンドとアウトバウンドのトラフィックを同時に許可でき、許可した通信の戻りは自動的に許可されます。Network ACLはサブネット単位で適用されるステートレスなフィルタで、インバウンドとアウトバウンドのルールを別々に設定します。デフォルトでは全トラフィックを許可しますが、明示的に許可・拒否を設定しないとブロックされません。実務では、個別インスタンスの細かい制御はSecurity Group、広域的なサブネットレベルの制御はNetwork ACLで行います。
(読んだらエンターキー! この内容から問題が出るよ)
❓ クエスチョン:
あなたは新卒エンジニアです。先輩から「オフィスのIPアドレスからだけSSHでアクセスできるようにしたい。対象は特定のEC2インスタンスだけで、同じサブネットにある他のインスタンスには影響させたくない」という要件を受けました。どの設定を行うのが最適ですか?
A. 対象インスタンスにだけSSH許可のインバウンドルールを追加したSecurity Groupを作成し、アタッチする。
B. サブネットのNetwork ACLにSSHを許可し、他のインバウンドトラフィックをすべて拒否するように設定する。
C. デフォルトのVPC Security Groupを無効化し、Network ACLだけで通信制御を行う。
D. SSHトラフィックをNATゲートウェイへ転送するルートテーブルエントリを追加する。
きみの答えは? [A/B/C/D] > A
🎉 正解!! さすが!
💡 正解: A. 対象インスタンスにだけSSH許可のインバウンドルールを追加したSecurity Groupを作成し、アタッチする。
【ポイント】
Security Groupはインスタンス単位で適用できるため、特定のEC2だけにSSH許可ルールを設定できます。Network ACLはサブネット全体に適用されるので、Bのように設定すると同じサブネット内の全インスタンスに影響します。CはSecurity Groupを無効化するとステートフルな戻りトラフィックが許可されず、接続が成立しません。Dはルートテーブルの役割であり、トラフィックの許可・拒否とは無関係です。
🌸 最初は覚えることが多いけど、実践すれば必ず身につきます。引き続き頑張ってください!
(今月の無料枠3,000回のうち、このセッションで 1 回使用)
もう1回ガチャを回す? [Enter=回す / q=やめる] >
さくらのAI Engineとは
さくらインターネットさんが提供している、生成AIをAPIで使えるサービスです。初心者的にありがたいポイントが3つありました。
1. 無料枠が「リクエスト数」で分かりやすい
月3,000リクエストまで無料。AIのAPIというと「トークン」という単位で課金されるのが一般的で、初心者には「これ、いくらかかってるの?」が正直わかりにくいです。その点、回数で数えられるのは体感としてすごくわかやすかったです。
2. 勝手に課金されない
無料枠を超えても自動的に課金されるのではなく、レートリミットがかかる方式。
課金の心配がなく自由に使える!
3. OpenAI互換
これが今回の一番のポイントで、後述しますが普通のopenaiライブラリがそのまま使えます。
セットアップ
1. アカウント作成とAPIキーの発行
さくらインターネット会員IDとさくらのクラウドプロジェクトを用意して、コントロールパネルから利用規約に同意し、プランを選択します。
その後、左メニューのアカウントトークン → 右上のアカウントトークンを作成でAPIキーが発行されます。
⚠️ ここ注意 トークンは作成時の一度しか表示されません。UUID:シークレット という形式の文字列なので、必ずその場でコピーしておきましょう。
2. ライブラリのインストール
pip install openai python-dotenv
3. APIキーを .env に書く
キーをコードに直接書くと、GitHubに上げたとき流出します(新人がやりがちな事故です!私もやってしまったことがあります)。.envファイルに分けておきます。
SAKURA_AI_API_KEY="APIキーを記入"
そして.gitignoreに.envを書いておく。ここまでがお作法とのことです。
.env
コード
さくらのAI Engineへの接続部分
from openai import OpenAI
client = OpenAI(
api_key=API_KEY,
base_url="https://api.ai.sakura.ad.jp/v1", # ← ここを差し替えるだけ!
)
response = client.chat.completions.create(
model="gpt-oss-120b",
messages=[{"role": "user", "content": "こんにちは"}],
)
OpenAI互換なので、base_urlを差し替えるだけで、いつものopenaiパッケージがそのまま動きます。
ガチャの抽選部分
AWS_KEYWORDS = [
# サービス系
"Amazon S3", "Amazon EC2", "AWS Lambda", "Amazon RDS", "Amazon DynamoDB",
# ...(中略)
]
# レアリティ抽選(★3はちょっとレア)
DIFFICULTIES = [
("★☆☆(ビギナー)", 50),
("★★☆(そこそこ)", 35),
("★★★(難問)", 15),
]
def spin_gacha():
keyword = random.choice(AWS_KEYWORDS)
difficulty = random.choices(
[d[0] for d in DIFFICULTIES],
weights=[d[1] for d in DIFFICULTIES],
)[0]
return keyword, difficulty
ここがこのツールの地味な工夫ポイントです。
最初は「AWSの問題をランダムに出して」とAIに丸投げしていたのですが、それだとS3とEC2ばかり出てきました。AIは「AWSといえば?」で有名どころを選んでしまうんですね。
なので、何を出題するかはPython側でランダムに決めて、AIには「このキーワードで問題を作って」と渡すようにしました。キーワードはAWSの公式の出題範囲から取得しました。
キーワード抽選はプログラム、文章作成はAI、という役割分担です。
難易度はrandom.choices()のweightsで重み付けして、★3が15%でちょっとレアにしています。ソシャゲっぽくて、ちょっとテンションが上がります。
AIへのお願い(プロンプト)
prompt = f"""あなたはAWSに詳しい優しい先輩エンジニアです。
新卒1年目のエンジニア向けに、AWSのキーワードについて「まず解説→そのあと4択クイズ」の教材を作ってください。
条件:
- 対象キーワード: {keyword}
- 難易度: {difficulty}
- lecture はこのキーワードのミニ解説(それが何か・何のために使うか・実務でどう登場するか)を新人向けに4〜6文で
- question は lecture で学んだ内容を実際に使えるかを試す4択問題にする。「あなたは新卒エンジニアです。先輩から〜と頼まれました」のような実務風のシナリオにする
- choices は選択肢4つ。まぎらわしい選択肢を混ぜて、正解の位置はランダムにする
- explanation は答えの理由と、他の選択肢がなぜ違うのかも分かるように具体的に。専門用語には軽く補足を入れる
- 最後に新人への励ましの一言を添える
次のJSON形式だけで答えてください(JSON以外の文章は出力しない):
{{
"lecture": "キーワードのミニ解説(4〜6文)",
"question": "問題文(シナリオのみ。選択肢は含めない)",
"choices": {{"A": "選択肢1", "B": "選択肢2", "C": "選択肢3", "D": "選択肢4"}},
"correct": "正解の記号(A/B/C/Dのどれか1文字)",
"explanation": "解説(3〜6文くらい)",
"cheer": "新人への励ましの一言"
}}"""
出題と答え合わせ
print("\n❓ クエスチョン:")
print(quiz["question"] + "\n")
for letter in ["A", "B", "C", "D"]:
print(f" {letter}. {quiz['choices'][letter]}")
# A〜Dの答えが入力されるまで聞き直す
while True:
choice = input("\nきみの答えは? [A/B/C/D] > ").strip().upper()
if choice in ("A", "B", "C", "D"):
break
print(" A・B・C・Dのどれかで答えてね!")
correct = quiz["correct"].strip().upper()
if choice == correct:
print("\n🎉 正解!! さすが!")
else:
print(f"\n😢 ざんねん! 正解は {correct} でした。")
.strip().upper()を付けているのは、小文字のbやスペース付きで入力しても正解にするためです。自分が使うツールなので、こういう細かいストレスは潰しておきました。
無料枠は本当にたっぷり
このツールは、1回のガチャで消費するAPIリクエストは1回だけです。解説・問題・選択肢・正解・解説文・励ましの言葉を、1回のリクエストでまとめて生成しているからです。
つまり月3,000リクエスト=3,000問。
- 1日100問解いても1か月もつ
- 毎日30問なら余裕でおつりがくる
正直、新人が1か月で3,000問もAWSの問題を解けるとは思えないので、実質使い切れない量です!
作ってみて分かったこと
生成AIのAPIは、思っていた10分の1くらい簡単でした。
やったことを分解すると、こうです。
- APIキーを発行する
-
base_urlをさくらのAI Engineに向ける - お願いしたいことを日本語で書いて投げる
- 返ってきた文字列を
json.loads()する
難しいのはAIに何をお願いするかのプロンプトを考えるところでした。「ランダムに問題を出して」と丸投げしたら同じ問題ばかりになったこと、JSONの形を指定したら急に扱いやすくなったこと。この2つは、実際に手を動かさないと分からなかったと思います。
今後やってみたいこと
- 間違えた問題のキーワードだけを集めた「弱点ガチャ」を作る
- ソシャゲみたいにキーワードコレクションみたいなのを作る
- 記述式の問題も出して、答えをAIに採点してもらう
- 問題保存機能を作る
これからもさくらのAI Engineを使い倒して色々触ってみようと思います!
全体のコード
import json
import os
import random
import sys
from dotenv import load_dotenv
from openai import OpenAI
# ── さくらのAI Engine への接続設定 ──────────────────────────
load_dotenv()
API_KEY = os.environ.get("SAKURA_AI_API_KEY")
BASE_URL = "https://api.ai.sakura.ad.jp/v1"
MODEL = "gpt-oss-120b" # 無償枠で月3,000リクエスト使えるモデル
# ── ガチャの中身 長くなるのでキーワードは省略します。 ────────────────────────────────────────────
AWS_KEYWORDS = [
# サービス系
"Amazon S3", "Amazon EC2", "AWS Lambda", "Amazon RDS", "Amazon DynamoDB",
# ...(中略)
]
# レアリティ抽選
DIFFICULTIES = [
("★☆☆(ビギナー)", 50),
("★★☆(そこそこ)", 35),
("★★★(難問・奇問)", 15),
]
def spin_gacha():
"""ガチャを1回まわして、出題条件(キーワード・難易度)を決める"""
keyword = random.choice(AWS_KEYWORDS)
difficulty = random.choices(
[d[0] for d in DIFFICULTIES],
weights=[d[1] for d in DIFFICULTIES],
)[0]
return keyword, difficulty
def generate_quiz(client, keyword, difficulty):
"""さくらのAI Engineに「キーワード解説+4択問題」をセットで作ってもらう(リクエスト1回だけ!)"""
prompt = f"""あなたはAWSに詳しい優しい先輩エンジニアです。
新卒1年目のエンジニア向けに、AWSのキーワードについて「まず解説→そのあと4択クイズ」の教材を作ってください。
条件:
- 対象キーワード: {keyword}
- 難易度: {difficulty}
- lecture はこのキーワードのミニ解説(それが何か・何のために使うか・実務でどう登場するか)を新人向けに4〜6文で
- question は lecture で学んだ内容を実際に使えるかを試す4択問題にする。「あなたは新卒エンジニアです。先輩から〜と頼まれました」のような実務風のシナリオにする
- choices は選択肢4つ。まぎらわしい選択肢を混ぜて、正解の位置はランダムにする
- explanation は答えの理由と、他の選択肢がなぜ違うのかも分かるように具体的に。専門用語には軽く補足を入れる
- 最後に新人への励ましの一言を添える
次のJSON形式だけで答えてください(JSON以外の文章は出力しない):
{{
"lecture": "キーワードのミニ解説(4〜6文)",
"question": "問題文(シナリオのみ。選択肢は含めない)",
"choices": {{"A": "選択肢1", "B": "選択肢2", "C": "選択肢3", "D": "選択肢4"}},
"correct": "正解の記号(A/B/C/Dのどれか1文字)",
"explanation": "解説(3〜6文くらい)",
"cheer": "新人への励ましの一言"
}}"""
response = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.9,
)
content = response.choices[0].message.content
content = content.strip().removeprefix("```json").removeprefix("```").removesuffix("```")
return json.loads(content)
def main():
if not API_KEY:
print("⚠️ APIキーが見つかりません!")
print(" (APIキーはさくらのAI Engineのコントロールパネルで発行できます)")
sys.exit(1)
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
count = 0
while True:
keyword, difficulty = spin_gacha()
print("\n🎰 今日のAWSキーワードガチャを回した! ガラガラ…\n")
print(f"【キーワード】 {keyword}")
print(f"【難易度】 {difficulty}\n")
print("🤖 さくらのAIが教材を作成中…\n")
try:
quiz = generate_quiz(client, keyword, difficulty)
except json.JSONDecodeError:
print("😵 AIの返事がJSONになってなかった…もう1回引いてみて!")
continue
count += 1
print(f"📖 まずはミニ解説:「{keyword}」って何?\n")
print(quiz["lecture"])
input("\n(読んだらエンターキー! この内容から問題が出るよ)")
print("\n❓ クエスチョン:")
print(quiz["question"] + "\n")
for letter in ["A", "B", "C", "D"]:
print(f" {letter}. {quiz['choices'][letter]}")
# A〜Dの答えが入力されるまで聞き直す
while True:
choice = input("\nきみの答えは? [A/B/C/D] > ").strip().upper()
if choice in ("A", "B", "C", "D"):
break
print(" A・B・C・Dのどれかで答えてね!")
correct = quiz["correct"].strip().upper()
if choice == correct:
print("\n🎉 正解!! さすが!")
else:
print(f"\n😢 ざんねん! 正解は {correct} でした。")
print(f"\n💡 正解: {correct}. {quiz['choices'][correct]}\n")
print(f"【ポイント】\n{quiz['explanation']}\n")
print(f"🌸 {quiz['cheer']}")
print(f"\n(今月の無料枠3,000回のうち、このセッションで {count} 回使用)")
again = input("\nもう1回ガチャを回す? [Enter=回す / q=やめる] > ")
if again.strip().lower() == "q":
print("\n👋 おつかれさま!また明日もガチャ回してAWSマスターになろう!")
break
if __name__ == "__main__":
main()