0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangChainを学びたい(Step3:独自のナレッジから検索する)

Posted at

はじめに

前回までの続きです。前回はGeminiが事前学習したデータを元に検索をしてもらいました。ただ、実際にビジネスの場面で使う場合はほとんどの場合が独自ナレッジを元に生成する事がほとんどだと思います。今回は独自のナレッジから検索できるように改良していきます!!

Hands-on

今回は独自のナレッジから検索するため、ポケモン図鑑を自作します。自作したポケモン図鑑の中から検索を実行し、対象のポケモンの説明を返却します!

成果物

pokemon_data.gif

コード

pokemon_data.json
[
    {
      "id": 25,
      "name_jp": "ピカチュウ",
      "name_en": "Pikachu",
      "types": ["でんき"],
      "height_m": 0.4,
      "weight_kg": 6.0,
      "description": "ほっぺたの電気袋に電気をためこむネズミポケモン。仲間どうしでしっぽを合わせて電気で合図を送りあう。"
    },
    {
      "id": 1,
      "name_jp": "フシギダネ",
      "name_en": "Bulbasaur",
      "types": ["くさ", "どく"],
      "height_m": 0.7,
      "weight_kg": 6.9,
      "description": "生まれたときから背中に植物のタネを背負っており、少しずつ大きく育っていく。"
    },
    {
      "id": 4,
      "name_jp": "ヒトカゲ",
      "name_en": "Charmander",
      "types": ["ほのお"],
      "height_m": 0.6,
      "weight_kg": 8.5,
      "description": "尻尾の先で燃えている炎は体調のバロメーター。元気がないと小さく、元気だと勢いよく燃え上がる。"
    },
    {
      "id": 7,
      "name_jp": "ゼニガメ",
      "name_en": "Squirtle",
      "types": ["みず"],
      "height_m": 0.5,
      "weight_kg": 9.0,
      "description": "甲羅にこもって身を守るカメポケモン。口から勢いよく水を吹き出して戦う。"
    }
  ]
pokemon_zukan_json.py
# pokemon_zukan_json.py
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
import os
import json
from typing import Any, Dict, List, Optional


# --------------------------
# 1. JSON から独自図鑑データを読み込む
# --------------------------
def load_pokemon_data(path: str = "pokemon_data.json") -> List[Dict[str, Any]]:
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)


def find_pokemon_by_name(name_jp: str, data: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    """日本語名で完全一致検索(とりあえずシンプルに)"""
    for entry in data:
        if entry["name_jp"] == name_jp:
            return entry
    return None


# --------------------------
# 2. LangChain / Gemini の準備
# --------------------------
load_dotenv()

model = ChatOpenAI(
    model="google/gemini-1.5-flash",          # OpenRouter 上の Gemini モデル
    openai_api_key=os.getenv("OPENROUTER_API_KEY"),
    base_url="https://openrouter.ai/api/v1",
)

# 「このデータだけを使って答えろ」と明示するプロンプト
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "あなたはポケモン図鑑です。"
            "与えられたデータだけを利用して回答してください。"
            "与えられていない情報を推測で補ったり、新しい事実を作ったりしてはいけません。"
        ),
        (
            "human",
            "以下のポケモンデータだけを使って、図鑑風に説明してください。\n\n"
            "{pokedex_entry}"
        ),
    ]
)

chain = prompt | model


# --------------------------
# 3. メイン処理
# --------------------------
def format_entry_for_prompt(entry: Dict[str, Any]) -> str:
    """LLM に渡すために、1匹分のデータをテキストに整形"""
    types = ", ".join(entry.get("types", []))
    return (
        f"図鑑番号: {entry.get('id')}\n"
        f"名前: {entry.get('name_jp')} ({entry.get('name_en')})\n"
        f"タイプ: {types}\n"
        f"高さ: {entry.get('height_m')} m\n"
        f"重さ: {entry.get('weight_kg')} kg\n"
        f"説明: {entry.get('description')}\n"
    )


def main() -> None:
    print("=== ポケモン図鑑(独自JSONナレッジ版 × Gemini) ===")
    print("※ この図鑑は pokemon_data.json に登録されているポケモンだけを参照します。")
    print("ポケモン名を入力してください(例: ピカチュウ, フシギダネ)")
    print("空で Enter を押すと終了します。")

    # JSON を読み込む(起動時に1回だけ)
    try:
        data = load_pokemon_data()
    except FileNotFoundError:
        print("pokemon_data.json が見つかりません。先に作成してください。")
        return

    while True:
        name = input("\nあなた > ").strip()
        if not name:
            print("終了します。")
            break

        entry = find_pokemon_by_name(name, data)
        if entry is None:
            print("\n図鑑 >")
            print("そのポケモンは、現在この図鑑データ(pokemon_data.json)には登録されていません。")
            print("pokemon_data.json に追記すれば、この図鑑からも参照できるようになります。")
            continue

        # 見つかったら、そのデータだけを LLM に渡して説明してもらう
        pokedex_entry_text = format_entry_for_prompt(entry)

        res = chain.invoke({"pokedex_entry": pokedex_entry_text})

        print("\n図鑑 >")
        print(res.content)


if __name__ == "__main__":
    main()

ちなみに、費用は0.000148ドルなので、0.02円くらいです。
image.png

さいごに

今回は名前の完全一致でやっていきました!次回以降でもっと難しくしていこうと思います!!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?