はじめに
前回までの続きです。前回はポケモンを6匹でしたが、今回はポケモンの数を増やしていきます。その場合にパフォーマンスにどう影響があるのか?を測定したいと思います!
30匹
30匹
import json
from typing import Any, Dict, List
# ハードコードされたポケモンデータ(30匹)
POKEMONS = [
{
"id": 25,
"name_jp": "ピカチュウ",
"name_en": "pikachu",
"types": [
"electric"
],
"height_m": 0.4,
"weight_kg": 6.0,
"base_stats": {
"hp": 35,
"attack": 55,
"defense": 40,
"sp_attack": 50,
"sp_defense": 50,
"speed": 90
},
"tags": [
"cute",
"kanto",
"mascot"
]
},
{
"id": 1,
"name_jp": "フシギダネ",
"name_en": "bulbasaur",
"types": [
"grass",
"poison"
],
"height_m": 0.7,
"weight_kg": 6.9,
"base_stats": {
"hp": 45,
"attack": 49,
"defense": 49,
"sp_attack": 65,
"sp_defense": 65,
"speed": 45
},
"tags": [
"starter",
"kanto"
]
},
{
"id": 6,
"name_jp": "リザードン",
"name_en": "charizard",
"types": [
"fire",
"flying"
],
"height_m": 1.7,
"weight_kg": 90.5,
"base_stats": {
"hp": 78,
"attack": 84,
"defense": 78,
"sp_attack": 109,
"sp_defense": 85,
"speed": 100
},
"tags": [
"starter",
"kanto",
"popular",
"attacker"
]
},
{
"id": 51,
"name_jp": "ダグトリオ",
"name_en": "dugtrio",
"types": [
"ground"
],
"height_m": 0.7,
"weight_kg": 33.3,
"base_stats": {
"hp": 35,
"attack": 80,
"defense": 50,
"sp_attack": 50,
"sp_defense": 70,
"speed": 120
},
"tags": [
"ground",
"fast",
"kanto"
]
},
{
"id": 143,
"name_jp": "カビゴン",
"name_en": "snorlax",
"types": [
"normal"
],
"height_m": 2.1,
"weight_kg": 460.0,
"base_stats": {
"hp": 160,
"attack": 110,
"defense": 65,
"sp_attack": 65,
"sp_defense": 110,
"speed": 30
},
"tags": [
"tank",
"kanto",
"heavy"
]
},
{
"id": 94,
"name_jp": "ゲンガー",
"name_en": "gengar",
"types": [
"ghost",
"poison"
],
"height_m": 1.5,
"weight_kg": 40.5,
"base_stats": {
"hp": 60,
"attack": 65,
"defense": 60,
"sp_attack": 130,
"sp_defense": 75,
"speed": 110
},
"tags": [
"kanto",
"special_attacker",
"fast"
]
},
{
"id": 2,
"name_jp": "フシギソウ",
"name_en": "ivysaur",
"types": [
"grass",
"poison"
],
"height_m": 1.0,
"weight_kg": 13.0,
"base_stats": {
"hp": 60,
"attack": 62,
"defense": 63,
"sp_attack": 80,
"sp_defense": 80,
"speed": 60
},
"tags": [
"kanto"
]
},
{
"id": 3,
"name_jp": "フシギバナ",
"name_en": "venusaur",
"types": [
"grass",
"poison"
],
"height_m": 2.0,
"weight_kg": 100.0,
"base_stats": {
"hp": 80,
"attack": 82,
"defense": 83,
"sp_attack": 100,
"sp_defense": 100,
"speed": 80
},
"tags": [
"kanto"
]
},
{
"id": 4,
"name_jp": "ヒトカゲ",
"name_en": "charmander",
"types": [
"fire"
],
"height_m": 0.6,
"weight_kg": 8.5,
"base_stats": {
"hp": 39,
"attack": 52,
"defense": 43,
"sp_attack": 60,
"sp_defense": 50,
"speed": 65
},
"tags": [
"kanto"
]
},
{
"id": 5,
"name_jp": "リザード",
"name_en": "charmeleon",
"types": [
"fire"
],
"height_m": 1.1,
"weight_kg": 19.0,
"base_stats": {
"hp": 58,
"attack": 64,
"defense": 58,
"sp_attack": 80,
"sp_defense": 65,
"speed": 80
},
"tags": [
"kanto"
]
},
{
"id": 7,
"name_jp": "ゼニガメ",
"name_en": "squirtle",
"types": [
"water"
],
"height_m": 0.5,
"weight_kg": 9.0,
"base_stats": {
"hp": 44,
"attack": 48,
"defense": 65,
"sp_attack": 50,
"sp_defense": 64,
"speed": 43
},
"tags": [
"kanto"
]
},
{
"id": 8,
"name_jp": "カメール",
"name_en": "wartortle",
"types": [
"water"
],
"height_m": 1.0,
"weight_kg": 22.5,
"base_stats": {
"hp": 59,
"attack": 63,
"defense": 80,
"sp_attack": 65,
"sp_defense": 80,
"speed": 58
},
"tags": [
"kanto"
]
},
{
"id": 9,
"name_jp": "カメックス",
"name_en": "blastoise",
"types": [
"water"
],
"height_m": 1.6,
"weight_kg": 85.5,
"base_stats": {
"hp": 79,
"attack": 83,
"defense": 100,
"sp_attack": 85,
"sp_defense": 105,
"speed": 78
},
"tags": [
"kanto"
]
},
{
"id": 10,
"name_jp": "キャタピー",
"name_en": "caterpie",
"types": [
"bug"
],
"height_m": 0.3,
"weight_kg": 2.9,
"base_stats": {
"hp": 45,
"attack": 30,
"defense": 35,
"sp_attack": 20,
"sp_defense": 20,
"speed": 45
},
"tags": [
"kanto"
]
},
{
"id": 11,
"name_jp": "トランセル",
"name_en": "metapod",
"types": [
"bug"
],
"height_m": 0.7,
"weight_kg": 9.9,
"base_stats": {
"hp": 50,
"attack": 20,
"defense": 55,
"sp_attack": 25,
"sp_defense": 25,
"speed": 30
},
"tags": [
"kanto"
]
},
{
"id": 12,
"name_jp": "バタフリー",
"name_en": "butterfree",
"types": [
"bug",
"flying"
],
"height_m": 1.1,
"weight_kg": 32.0,
"base_stats": {
"hp": 60,
"attack": 45,
"defense": 50,
"sp_attack": 90,
"sp_defense": 80,
"speed": 70
},
"tags": [
"kanto"
]
},
{
"id": 13,
"name_jp": "ビードル",
"name_en": "weedle",
"types": [
"bug",
"poison"
],
"height_m": 0.3,
"weight_kg": 3.2,
"base_stats": {
"hp": 40,
"attack": 35,
"defense": 30,
"sp_attack": 20,
"sp_defense": 20,
"speed": 50
},
"tags": [
"kanto"
]
},
{
"id": 14,
"name_jp": "コクーン",
"name_en": "kakuna",
"types": [
"bug",
"poison"
],
"height_m": 0.6,
"weight_kg": 10.0,
"base_stats": {
"hp": 45,
"attack": 25,
"defense": 50,
"sp_attack": 25,
"sp_defense": 25,
"speed": 35
},
"tags": [
"kanto"
]
},
{
"id": 15,
"name_jp": "スピアー",
"name_en": "beedrill",
"types": [
"bug",
"poison"
],
"height_m": 1.0,
"weight_kg": 29.5,
"base_stats": {
"hp": 65,
"attack": 90,
"defense": 40,
"sp_attack": 45,
"sp_defense": 80,
"speed": 75
},
"tags": [
"kanto"
]
},
{
"id": 16,
"name_jp": "ポッポ",
"name_en": "pidgey",
"types": [
"normal",
"flying"
],
"height_m": 0.3,
"weight_kg": 1.8,
"base_stats": {
"hp": 40,
"attack": 45,
"defense": 40,
"sp_attack": 35,
"sp_defense": 35,
"speed": 56
},
"tags": [
"kanto"
]
},
{
"id": 17,
"name_jp": "ピジョン",
"name_en": "pidgeotto",
"types": [
"normal",
"flying"
],
"height_m": 1.1,
"weight_kg": 30.0,
"base_stats": {
"hp": 63,
"attack": 60,
"defense": 55,
"sp_attack": 50,
"sp_defense": 50,
"speed": 71
},
"tags": [
"kanto"
]
},
{
"id": 18,
"name_jp": "ピジョット",
"name_en": "pidgeot",
"types": [
"normal",
"flying"
],
"height_m": 1.5,
"weight_kg": 39.5,
"base_stats": {
"hp": 83,
"attack": 80,
"defense": 75,
"sp_attack": 70,
"sp_defense": 70,
"speed": 101
},
"tags": [
"kanto"
]
},
{
"id": 19,
"name_jp": "コラッタ",
"name_en": "rattata",
"types": [
"normal"
],
"height_m": 0.3,
"weight_kg": 3.5,
"base_stats": {
"hp": 30,
"attack": 56,
"defense": 35,
"sp_attack": 25,
"sp_defense": 35,
"speed": 72
},
"tags": [
"kanto"
]
},
{
"id": 20,
"name_jp": "ラッタ",
"name_en": "raticate",
"types": [
"normal"
],
"height_m": 0.7,
"weight_kg": 18.5,
"base_stats": {
"hp": 55,
"attack": 81,
"defense": 60,
"sp_attack": 50,
"sp_defense": 70,
"speed": 97
},
"tags": [
"kanto"
]
},
{
"id": 21,
"name_jp": "オニスズメ",
"name_en": "spearow",
"types": [
"normal",
"flying"
],
"height_m": 0.3,
"weight_kg": 2.0,
"base_stats": {
"hp": 40,
"attack": 60,
"defense": 30,
"sp_attack": 31,
"sp_defense": 31,
"speed": 70
},
"tags": [
"kanto"
]
},
{
"id": 22,
"name_jp": "オニドリル",
"name_en": "fearow",
"types": [
"normal",
"flying"
],
"height_m": 1.2,
"weight_kg": 38.0,
"base_stats": {
"hp": 65,
"attack": 90,
"defense": 65,
"sp_attack": 61,
"sp_defense": 61,
"speed": 100
},
"tags": [
"kanto"
]
},
{
"id": 23,
"name_jp": "アーボ",
"name_en": "ekans",
"types": [
"poison"
],
"height_m": 2.0,
"weight_kg": 6.9,
"base_stats": {
"hp": 35,
"attack": 60,
"defense": 44,
"sp_attack": 40,
"sp_defense": 54,
"speed": 55
},
"tags": [
"kanto"
]
},
{
"id": 24,
"name_jp": "アーボック",
"name_en": "arbok",
"types": [
"poison"
],
"height_m": 3.5,
"weight_kg": 65.0,
"base_stats": {
"hp": 60,
"attack": 85,
"defense": 69,
"sp_attack": 65,
"sp_defense": 79,
"speed": 80
},
"tags": [
"kanto"
]
},
{
"id": 27,
"name_jp": "ニドラン♀",
"name_en": "nidoran_f",
"types": [
"poison"
],
"height_m": 0.4,
"weight_kg": 7.0,
"base_stats": {
"hp": 55,
"attack": 47,
"defense": 52,
"sp_attack": 40,
"sp_defense": 40,
"speed": 41
},
"tags": [
"kanto"
]
},
{
"id": 28,
"name_jp": "ニドリーナ",
"name_en": "nidorina",
"types": [
"poison"
],
"height_m": 0.8,
"weight_kg": 20.0,
"base_stats": {
"hp": 70,
"attack": 62,
"defense": 67,
"sp_attack": 55,
"sp_defense": 55,
"speed": 56
},
"tags": [
"kanto"
]
},
{
"id": 29,
"name_jp": "ニドクイン",
"name_en": "nidoqueen",
"types": [
"poison",
"ground"
],
"height_m": 1.3,
"weight_kg": 60.0,
"base_stats": {
"hp": 90,
"attack": 82,
"defense": 87,
"sp_attack": 75,
"sp_defense": 85,
"speed": 76
},
"tags": [
"kanto"
]
},
{
"id": 30,
"name_jp": "ニドラン♂",
"name_en": "nidoran_m",
"types": [
"poison"
],
"height_m": 0.5,
"weight_kg": 9.0,
"base_stats": {
"hp": 46,
"attack": 57,
"defense": 40,
"sp_attack": 40,
"sp_defense": 40,
"speed": 50
},
"tags": [
"kanto"
]
}
]
def type_match(pokemon_types, required_types, exclude_types) -> bool:
# required_types: いずれか1つでも含まれていればOK(OR条件)
if required_types:
if not any(t in pokemon_types for t in required_types):
return False
# exclude_types: どれか含んでいたらNG
if exclude_types:
if any(t in pokemon_types for t in exclude_types):
return False
return True
def tags_match(pokemon_tags, required_tags) -> bool:
if not required_tags:
return True
return any(tag in pokemon_tags for tag in required_tags)
def _parse_filter_json(llm_raw: str) -> Dict[str, Any]:
raw = llm_raw.strip()
# もし ```json 〜 ``` で囲まれていたら中身だけ取り出す
if raw.startswith("```"):
lines = raw.splitlines()
if lines and lines[0].startswith("```"):
lines = lines[1:]
if lines and lines[-1].startswith("```"):
lines = lines[:-1]
raw = "\n".join(lines).strip()
return json.loads(raw)
def main(llm_raw: str) -> Dict[str, Any]:
# ① LLMのJSONをパース
cond = _parse_filter_json(llm_raw)
types = cond.get("types") or []
exclude_types = cond.get("exclude_types") or []
min_speed = cond.get("min_speed")
min_attack = cond.get("min_attack")
max_height_m = cond.get("max_height_m")
max_weight_kg = cond.get("max_weight_kg")
required_tags = cond.get("tags") or []
sort_by = cond.get("sort_by") or "speed"
sort_order = cond.get("sort_order") or "desc"
limit = cond.get("limit") or 5
candidates = []
for p in POKEMONS:
if not type_match(p["types"], types, exclude_types):
continue
stats = p["base_stats"]
if min_speed is not None and stats["speed"] < float(min_speed):
continue
if min_attack is not None and stats["attack"] < float(min_attack):
continue
if max_height_m is not None and p["height_m"] > float(max_height_m):
continue
if max_weight_kg is not None and p["weight_kg"] > float(max_weight_kg):
continue
if not tags_match(p["tags"], required_tags):
continue
candidates.append(p)
# ソート
def sort_key(p):
if sort_by == "speed":
return p["base_stats"]["speed"]
if sort_by == "attack":
return p["base_stats"]["attack"]
if sort_by == "height_m":
return p["height_m"]
if sort_by == "weight_kg":
return p["weight_kg"]
return 0
reverse = (sort_order == "desc")
candidates.sort(key=sort_key, reverse=reverse)
results = candidates[: int(limit)]
# ③ ユーザー向けメッセージ生成(テキストだけ返す)
if not results:
message_lines = [
"条件に合うポケモンは見つかりませんでした。",
"・タイプや「素早い」「小さい」などの条件を少しゆるめて、もう一度聞いてみてください。",
]
return {"message": "\n".join(message_lines)}
header = "検索結果(最大 {} 匹):".format(len(results))
lines = [header]
for i, p in enumerate(results, start=1):
stats = p["base_stats"]
line = (
f"{i}. No.{p['id']} {p['name_jp']} / {p['name_en']}\n"
f" タイプ: {', '.join(p['types'])}\n"
f" 身長: {p['height_m']}m / 体重: {p['weight_kg']}kg\n"
f" 攻撃: {stats['attack']} / 素早さ: {stats['speed']}"
)
lines.append(line)
return {"message": "\n".join(lines)}
ポケモンの検索は0.7秒くらいですね。
50匹
100匹
151匹
→もっと遅いかと思ったんですが、そこまで遅くならないですね。
まぁ、いいですね。本当はめっちゃおそいからRAGとかに置き換えようとかの話に繋げようと思ってました。






