0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PokeAPIで取得したCSVをRAGに食わせてローカルAIチャットを作る

0
Posted at

はじめに
この記事では以下を一から作ります。
PokeAPIからポケモンの種族値データを取得してCSVに保存
そのCSVをRAG(検索拡張生成)用のベクトルDBに変換
ローカルLLMに食わせてチャットで質問できるWebアプリを作る
完成するとこんな質問ができます

Q: 素早さが最も高いポケモンは?
Q: HPが100以上で合計種族値が高いポケモンを教えて
Q: リザードンとガブリアスの種族値を比較して

すべてローカルで動くため、APIキー不要・通信費ゼロ・データが外に出ないのが特徴です。

動作環境
項目 内容
マシン iMac M1
メモリ 16GB
ストレージ 1TB
OS macOS Sonoma
Python 3.14(Homebrew)
M2・M3・M4でも同様に動きます。Intelマックでも動きますが推論速度は遅くなります。

全体の仕組み

ユーザーの質問
      │
      ▼
┌─────────────────────────────┐
│   Python RAGアプリ           │
│   LangChain + Gradio        │
└────────┬──────────┬─────────┘
         │          │
         ▼          ▼
   ┌──────────┐  ┌──────────────┐
   │ChromaDB  │  │   Ollama     │
   │ベクトル検索│  │ LLM推論      │
   │1350件    │  │ Metal GPU    │
   └──────────┘  └──────────────┘
         │          │
         └────┬─────┘
              ▼
           回答を返す

RAGとは?
RAG(Retrieval-Augmented Generation) とは、LLMが回答を生成する前に関連データを検索してコンテキストとして渡す仕組みです。
通常のLLMは学習データにないことは答えられませんが、RAGを使うと「自分で用意したデータ」をもとに回答させることができます。

通常のLLM:  質問 → LLM → 回答(学習データのみ)
RAG:        質問 → 検索DB → 関連データ取得 → LLM → 回答(自分のデータをもとに)

使用技術 役割 ツール 選んだ理由
LLM Qwen2.5:7b 7Bクラス最高水準の日本語性能・4.7GBでM1 16GBに余裕
埋め込みモデル nomic-embed-text 軽量275MB・高精度・Ollamaで一元管理できる
LLM実行環境 Ollama Apple Silicon Metalに完全対応・モデル管理が楽
ベクトルDB ChromaDB ローカル永続保存・Pythonだけで完結・無料
RAGフレームワーク LangChain(LCEL) 業界標準・拡張しやすい
チャットUI Gradio 数行で動くWebチャットUI
データ取得 PokeAPI 無料・認証不要・全ポケモン情報が取れる

Part 1: 環境構築
1-1. Homebrewのインストール
macOSのパッケージマネージャーです。すでに入っている場合はスキップしてください。

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

インストール後、M1の場合はPATHを通す必要があります。

echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

確認:

brew --version
# Homebrew 4.x.x と表示されればOK

1-2. Pythonのインストール

brew install python

確認:

python3 --version
# Python 3.x.x と表示されればOK

1-3. Ollamaのインストール
OllamaはローカルでLLMを動かすためのツールです。モデルのダウンロードや管理、APIサーバーの起動をすべて担当します。

brew install ollama

または公式サイト(https://ollama.com)からdmgをダウンロードしてインストールすることもできます。
確認:

ollama --version
# ollama version 0.x.x と表示されればOK

1-4. Ollamaのモデルダウンロード
LLM本体と埋め込みモデルの2つをダウンロードします。

# チャット用LLM(約4.7GB・ダウンロードに5〜10分かかる)
ollama pull qwen2.5:7b

# 埋め込み用モデル(約275MB)
ollama pull nomic-embed-text

Qwen2.5:7bを選んだ理由
7Bクラスのモデルの中で日本語の理解・生成性能が最も高いモデルの一つです。M1 16GBのメモリで余裕をもって動作し、推論速度も実用的な速さです。
確認(モデル一覧):

ollama list
# NAME                    ID              SIZE    MODIFIED
# qwen2.5:7b              ...             4.7 GB  ...
# nomic-embed-text        ...             274 MB  ...

1-5. Ollamaサーバーの起動設定
Ollamaはサーバーとして動作するため、アプリを使う前に起動しておく必要があります。
方法A: Mac起動時に自動起動(推奨)

brew services start ollama

これでMacを再起動してもOllamaが自動で起動します。
方法B: 手動で起動(作業時だけ使う場合)

ollama serve

このターミナルは閉じずにそのままにしておく必要があります。アプリを使い終わったら Ctrl + C で停止できます。
動作確認(ブラウザでアクセス):

http://localhost:11434

Ollama is running と表示されればOKです。

1-6. プロジェクトフォルダの作成

mkdir ~/Desktop/myprogram/CSVDate
cd ~/Desktop/myprogram/CSVDate

1-7. Python仮想環境の作成
なぜ仮想環境が必要か?
macOS SonomaではHomebrewで管理されているPythonのシステム環境を保護するため、直接 pip install しようとすると以下のエラーが出ます。

error: externally-managed-environment

仮想環境を使うことでシステムのPythonを汚さずに済みます。プロジェクトごとにライブラリを管理できるため、将来的にも管理がしやすくなります。

# プロジェクトフォルダにいることを確認
pwd
# /Users/ユーザー名/Desktop/myprogram/CSVDate

# 仮想環境を作成(venvという名前のフォルダが作られる)
python3 -m venv venv

# 仮想環境を有効化
source venv/bin/activate

有効化すると、ターミナルの先頭に (venv) が表示されます。

(venv) wakuwaku@WakugawanoMac-mini CSVDate %

重要: ターミナルを新しく開くたびに source venv/bin/activate を実行する必要があります。

1-8. Pythonライブラリのインストール
仮想環境が有効な状態((venv) が表示されている状態)でインストールします。

pip install langchain langchain-ollama langchain-chroma chromadb gradio pandas requests

各ライブラリの役割:
ライブラリ 役割
langchain RAGパイプライン構築のコアフレームワーク
langchain-ollama LangChainからOllamaを使うためのアダプター
langchain-chroma LangChainからChromaDBを使うためのアダプター
chromadb ローカルベクトルデータベース本体
gradio WebチャットUIを数行で作れるライブラリ
pandas CSVの読み込み・加工
requests PokeAPIへのHTTPリクエスト
インストール確認:

pip list | grep -E "langchain|chroma|gradio|pandas"

Part 2: ポケモンCSVデータの取得
2-1. PokeAPIについて
PokeAPI(https://pokeapi.co)は認証不要で使えるポケモン情報APIです。
今回は以下の2種類のエンドポイントを使います。
https://pokeapi.co/api/v2/pokemon?limit=1500 — ポケモン一覧取得
https://pokeapi.co/api/v2/pokemon/{id} — 個別の種族値取得
https://pokeapi.co/api/v2/pokemon-species/{id} — 日本語名取得
2-2. スクリプトの解説
日本語名の取得
PokeAPIの日本語名には2種類あります。
言語コード 内容 例
ja-Hrkt カタカナ表記 リザードン
ja 漢字混じり表記 リザードン
すべてのポケモンに ja-Hrkt があるとは限らないため、ja-Hrktja の順でフォールバックします。

for lang_code in ['ja-Hrkt', 'ja']:
    for name_data in data['names']:
        if name_data['language']['name'] == lang_code:
            jp_name = name_data['name']
            break
    if jp_name:
        break

フォルム名の日本語変換
メガシンカやリージョンフォームはAPIの英語名(例: charizard-mega-x)を日本語に変換します。

FORM_JP_SUFFIX = {
    'mega-x': 'メガ{name}X',
    'mega-y': 'メガ{name}Y',
    'mega': 'メガ{name}',
    'primal': 'ゲンシ{name}',
    'alola': '{name}(アローラのすがた)',
    'galar': '{name}(ガラルのすがた)',
    'hisui': '{name}(ヒスイのすがた)',
    'paldea': '{name}(パルデアのすがた)',
    'gmax': '{name}(キョダイマックス)',
}

APIへの負荷対策
1350件のデータを取得するため、以下の対策をしています。
time.sleep(0.15) で各リクエスト間に待機時間を設ける
species_cache で同じURLへの重複リクエストをキャッシュ
リトライ処理(最大3回)でネットワークエラーに対応
HTTPステータス429(レート制限)を検知して5秒待機
2-3. スクリプト全文
all_pokemon_stats.py としてプロジェクトフォルダに保存します。

# -*- coding: utf-8 -*-
import requests
import csv
import time

SESSION = requests.Session()
SESSION.headers.update({'User-Agent': 'pokemon-stats-fetcher/1.0'})

def get_with_retry(url, retries=3, wait=1.0):
    """リトライ付きGETリクエスト"""
    for attempt in range(retries):
        try:
            res = SESSION.get(url, timeout=10)
            if res.status_code == 429:  # レート制限
                print(f"  レート制限。{wait * 5}秒待機...")
                time.sleep(wait * 5)
                continue
            res.raise_for_status()
            return res.json()
        except Exception as e:
            if attempt < retries - 1:
                time.sleep(wait * (attempt + 1))
            else:
                raise e
    return None

def get_japanese_name(species_url, species_cache):
    """ポケモンの日本語名を取得する(キャッシュ利用)"""
    if species_url in species_cache:
        return species_cache[species_url]
    try:
        data = get_with_retry(species_url)
        if data is None:
            return None
        jp_name = None
        # 優先順位: ja-Hrkt(カタカナ) → ja(漢字混じり)
        for lang_code in ['ja-Hrkt', 'ja']:
            for name_data in data['names']:
                if name_data['language']['name'] == lang_code:
                    jp_name = name_data['name']
                    break
            if jp_name:
                break
        species_cache[species_url] = jp_name
        time.sleep(0.15)  # サーバー負荷軽減
        return jp_name
    except Exception as e:
        print(f"  日本語名取得エラー ({species_url}): {e}")
        species_cache[species_url] = None
        return None

# フォルム名の日本語変換マップ
FORM_JP_SUFFIX = {
    'mega-x':   'メガ{name}X',
    'mega-y':   'メガ{name}Y',
    'mega':     'メガ{name}',
    'primal':   'ゲンシ{name}',
    'alola':    '{name}(アローラのすがた)',
    'galar':    '{name}(ガラルのすがた)',
    'hisui':    '{name}(ヒスイのすがた)',
    'paldea':   '{name}(パルデアのすがた)',
    'gmax':     '{name}(キョダイマックス)',
    'eternamax':'{name}(エタマックス)',
    'origin':   '{name}(オリジンフォルム)',
    'sky':      '{name}(スカイフォルム)',
    'therian':  '{name}(霊獣フォルム)',
    'ultra':    '{name}(ウルトラ)',
}

def build_jp_display_name(form_name, base_jp_name):
    """フォルム名から日本語表示名を生成"""
    if base_jp_name is None:
        return form_name
    parts = form_name.split('-', 1)
    suffix = parts[1] if len(parts) > 1 else ''
    if suffix in FORM_JP_SUFFIX:
        return FORM_JP_SUFFIX[suffix].replace('{name}', base_jp_name)
    for key, template in FORM_JP_SUFFIX.items():
        if suffix.startswith(key):
            return template.replace('{name}', base_jp_name)
    return base_jp_name

def main():
    output_filename = 'all_pokemon_stats.csv'
    print("ポケモンのリストを取得中...")
    response = get_with_retry("https://pokeapi.co/api/v2/pokemon?limit=1500")
    pokemon_list = response['results']
    print(f"合計 {len(pokemon_list)} 件のポケモンを取得しました。")

    species_cache = {}
    success_count = 0
    error_count = 0

    with open(output_filename, mode='w', encoding='utf-8-sig', newline='') as f:
        # utf-8-sig: ExcelでBOM付きUTF-8として認識させるため文字化け防止
        writer = csv.writer(f)
        writer.writerow(['図鑑番号', '名前', 'HP', 'こうげき', 'ぼうぎょ',
                         'とくこう', 'とくぼう', 'すばやさ', '合計'])

        for i, p in enumerate(pokemon_list):
            try:
                p_data = get_with_retry(p['url'])
                if p_data is None:
                    raise ValueError("データ取得失敗")

                stats = {s['stat']['name']: s['base_stat'] for s in p_data['stats']}
                hp      = stats.get('hp', 0)
                atk     = stats.get('attack', 0)
                defense = stats.get('defense', 0)
                sp_atk  = stats.get('special-attack', 0)
                sp_def  = stats.get('special-defense', 0)
                speed   = stats.get('speed', 0)
                total   = hp + atk + defense + sp_atk + sp_def + speed

                base_jp_name = get_japanese_name(p_data['species']['url'], species_cache)
                jp_name = build_jp_display_name(p['name'], base_jp_name)

                writer.writerow([p_data['id'], jp_name, hp, atk, defense,
                                 sp_atk, sp_def, speed, total])
                success_count += 1

                if (i + 1) % 100 == 0:
                    print(f"  ... {i+1}/{len(pokemon_list)} 件処理完了"
                          f"(成功: {success_count}, エラー: {error_count}")

            except Exception as e:
                print(f"  [ERROR] {p['name']}: {e}")
                error_count += 1

    print(f"\n完了! '{output_filename}' を確認してください。")
    print(f"成功: {success_count} 件 / エラー: {error_count}")

if __name__ == "__main__":
    main()

2-4. 実行

python all_pokemon_stats.py

完了まで約10〜20分かかります(API制限のためウェイトを入れているため)。
出力例:

ポケモンのリストを取得中...
合計 1350 件のポケモンを取得しました。
  ... 100/1350 件処理完了(成功: 98, エラー: 2)
  ... 200/1350 件処理完了(成功: 197, エラー: 3)
  ...
完了! 'all_pokemon_stats.csv' を確認してください。
成功: 1348 件 / エラー: 2 件

生成されたCSVの中身(先頭3行):

図鑑番号,名前,HP,こうげき,ぼうぎょ,とくこう,とくぼう,すばやさ,合計
1,フシギダネ,45,49,49,65,65,45,318
2,フシギソウ,60,62,63,80,80,60,405
6,リザードン,78,84,78,109,85,100,534

Part 3: ベクトルDBへの取り込み(ingest.py)
3-1. 埋め込み(Embedding)とは?
テキストを数値のベクトル(多次元の座標)に変換する技術です。意味が近い文章は近い座標に変換されます。

「リザードン」  → [0.12, -0.34, 0.89, ...]  ┐
「ファイヤー」  → [0.11, -0.31, 0.87, ...]  ┘ 近い(どちらも炎タイプ)

「カビゴン」    → [-0.78, 0.56, -0.12, ...] ← 遠い

ChromaDBはこのベクトルを保存し、質問文を同じくベクトルに変換して近いものを検索します。
3-2. スクリプト全文
ingest.py としてプロジェクトフォルダに保存します。

# -*- coding: utf-8 -*-
import pandas as pd
from langchain_core.documents import Document   # ※langchain.schema は旧パス(非推奨)
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma

# CSVを読み込んでDocumentリストに変換
df = pd.read_csv("all_pokemon_stats.csv", encoding="utf-8-sig")
docs = []

for _, row in df.iterrows():
    # LLMに渡しやすいテキスト形式でDocumentを作成
    content = (
        f"図鑑番号: {row['図鑑番号']}  名前: {row['名前']}\n"
        f"HP:{row['HP']} 攻:{row['こうげき']} 防:{row['ぼうぎょ']} "
        f"特攻:{row['とくこう']} 特防:{row['とくぼう']} 素早:{row['すばやさ']} "
        f"合計:{row['合計']}"
    )
    docs.append(Document(
        page_content=content,
        metadata={"name": row["名前"]}
    ))

print(f"{len(docs)} 件のドキュメントを作成しました。埋め込み生成を開始します...")

# nomic-embed-textで埋め込みを生成してChromaDBに保存
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma.from_documents(
    docs,
    embeddings,
    persist_directory="./chroma_db"   # このフォルダにデータが永続保存される
)

print(f"{len(docs)} 件のポケモンをインデックス化しました")
print("chroma_db/ フォルダを確認してください")

3-3. 実行前の確認事項
Ollamaが起動しているか確認してください。

curl http://localhost:11434
# "Ollama is running" と表示されればOK

起動していない場合:

# 自動起動を設定していない場合は手動で起動
ollama serve

3-4. 実行

python ingest.py

出力例:

1350 件のドキュメントを作成しました。埋め込み生成を開始します...
✅ 1350 件のポケモンをインデックス化しました
chroma_db/ フォルダを確認してください

完了後、chroma_db/ フォルダが作成されます。このフォルダがベクトルDBの実体です。
このスクリプトは初回のみ実行します。 chroma_db/ が残っている限り再実行は不要です。

Part 4: チャットアプリの作成(app.py)
4-1. LCELとは?
LangChain Expression Language(LCEL)は |(パイプ)演算子でコンポーネントをつなぐ記法です。

chain = retriever | format_docs | prompt | llm | output_parser

旧来の RetrievalQA.from_chain_type() は非推奨になっており、現在はLCELが推奨されています。
4-2. RAGの処理フロー

ユーザーの質問(例:「素早さが最も高いのは?」)
         │
         ▼
retriever: ChromaDBで意味的に近いポケモンデータを5件取得
         │
         ▼
format_docs: 5件のドキュメントを1つのテキストに結合
         │
         ▼
prompt: 「以下のポケモンデータを参考に日本語で答えてください」
        + 取得した5件のデータ
        + ユーザーの質問
         │
         ▼
llm(Qwen2.5:7b): コンテキストをもとに回答生成
         │
         ▼
StrOutputParser: LLMの出力を文字列に変換
         │
         ▼
Gradio: 回答をWebチャットに表示

4-3. スクリプト全文
app.py としてプロジェクトフォルダに保存します。
:::note warn
日本語文字列を直接テンプレートに書くと環境によっては SyntaxError: invalid character が発生するため、Unicodeエスケープを使用しています。
:::

# -*- coding: utf-8 -*-
import gradio as gr
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain_chroma import Chroma
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# --- ChromaDB(ベクトルDB)の読み込み ---
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma(
    persist_directory="./chroma_db",
    embedding_function=embeddings
)

# k=5: 質問に近いポケモンデータを5件取得する
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# --- LLMの設定 ---
# temperature=0: 毎回安定した回答を生成(0に近いほど決定論的)
llm = ChatOllama(model="qwen2.5:7b", temperature=0)

# --- プロンプトテンプレート ---
# {context}: ChromaDBから取得したポケモンデータが入る
# {question}: ユーザーの質問が入る
# ※日本語文字列はUnicodeエスケープで記述(文字化け対策)
TEMPLATE = (
    "\u4ee5\u4e0b\u306e\u30dd\u30b1\u30e2\u30f3\u30c7\u30fc\u30bf\u3092\u53c2\u8003\u306b\u3001"
    "\u65e5\u672c\u8a9e\u3067\u7b54\u3048\u3066\u304f\u3060\u3055\u3044\u3002\n\n"
    "[\u53c2\u8003\u30c7\u30fc\u30bf]\n{context}\n\n"
    "[\u8cea\u554f]\n{question}\n\n"
    "[\u56de\u7b54]"
)
# 上記は以下と同じ意味:
# "以下のポケモンデータを参考に、日本語で答えてください。\n\n
#  [参考データ]\n{context}\n\n[質問]\n{question}\n\n[回答]"

prompt = PromptTemplate(
    input_variables=["context", "question"],
    template=TEMPLATE
)

def format_docs(docs):
    """取得した複数のDocumentを1つのテキストに結合する"""
    return "\n\n".join(doc.page_content for doc in docs)

# --- LCELチェーンの構築 ---
# 旧: RetrievalQA.from_chain_type()(非推奨)
# 新: LCEL(| 演算子でつなぐ記法)
chain = (
    {
        "context": retriever | format_docs,  # 検索結果をテキストに変換
        "question": RunnablePassthrough()    # 質問をそのまま渡す
    }
    | prompt    # プロンプトテンプレートに埋め込む
    | llm       # LLMで回答生成
    | StrOutputParser()  # 出力を文字列に変換
)

def chat(message, history):
    """Gradioのチャット関数"""
    return chain.invoke(message)

# --- GradioでWebUIを起動 ---
gr.ChatInterface(
    chat,
    title="Pokemon Stats AI Chat",
    description="Ask about Pokemon base stats in Japanese",
    examples=[
        "\u7d20\u65e9\u3055\u304c\u6700\u3082\u9ad8\u3044\u30dd\u30b1\u30e2\u30f3\u306f\uff1f",
        "HP\u304c100\u4ee5\u4e0b\u3067\u5408\u8a08\u304c\u9ad8\u3044\u30dd\u30b1\u30e2\u30f3\u3092\u6559\u3048\u3066",
        "\u30ea\u30b6\u30fc\u30c9\u30f3\u3068\u30ac\u30d6\u30ea\u30a2\u30b9\u306e\u7a2e\u65cf\u5024\u3092\u6bd4\u8f03\u3057\u3066",
    ]
).launch()

4-4. 実行

python app.py

起動後に以下が表示されます:

Running on local URL:  http://127.0.0.1:7860

ブラウザで http://localhost:7860 を開くとチャット画面が表示されます。

Part 5: ハマりポイントと解決策
実際に構築した中でつまずいた箇所をまとめます。
エラー1: ModuleNotFoundError: No module named 'langchain.schema'
原因: LangChainのバージョンアップでモジュールの場所が変わった。

# ❌ 旧パス(エラーになる)
from langchain.schema import Document

# ✅ 新パス
from langchain_core.documents import Document

エラー2: ModuleNotFoundError: No module named 'langchain.chains'
原因: RetrievalQA が旧APIで非推奨になった。

# ❌ 旧記法
from langchain.chains import RetrievalQA
qa = RetrievalQA.from_chain_type(llm=llm, ...)

# ✅ 新記法(LCEL)
from langchain_core.runnables import RunnablePassthrough
chain = ({"context": retriever | format_docs, "question": RunnablePassthrough()}
         | prompt | llm | StrOutputParser())

エラー3: ConnectionError: Failed to connect to Ollama
原因: Ollamaサーバーが起動していない。

# 確認
curl http://localhost:11434

# 起動(別ターミナルで)
ollama serve

# または常駐設定
brew services start ollama

エラー4: error: externally-managed-environment
原因: HomebrewのPythonに直接pipしようとした。

# ✅ 仮想環境を作って使う
python3 -m venv venv
source venv/bin/activate
pip install ...

エラー5: SyntaxError: invalid character '、' (U+3001)
原因: ファイルの文字コードが正しく認識されず、全角文字がSyntaxErrorになった。
解決策: 日本語文字列をUnicodeエスケープで書く。

# ❌ 環境によってはSyntaxError
template = "以下のデータを参考に、日本語で答えてください。"

# ✅ Unicodeエスケープで記述
template = "\u4ee5\u4e0b\u306e\u30c7\u30fc\u30bf\u3092\u53c2\u8003\u306b\u3001\u65e5\u672c\u8a9e\u3067\u7b54\u3048\u3066\u304f\u3060\u3055\u3044\u3002"

CSVをExcelで開くと文字化けする
原因: UTF-8のBOMなしファイルをExcelが認識できない。

# ❌ 文字化けする
open(filename, encoding='utf-8')

# ✅ BOM付きUTF-8にする
open(filename, encoding='utf-8-sig')

Part 6: 完成後のフォルダ構成と起動手順
フォルダ構成

CSVDate/
├── venv/                      # Python仮想環境(gitignoreに追加推奨)
├── chroma_db/                 # ChromaDBのデータ(gitignoreに追加推奨)
│   ├── chroma.sqlite3
│   └── ...
├── all_pokemon_stats.csv      # 取得したポケモンデータ(1350件)
├── all_pokemon_stats.py       # Step2: CSV取得スクリプト
├── ingest.py                  # Step3: ベクトルDB構築スクリプト
└── app.py                     # Step4: チャットアプリ

2回目以降の起動手順

# 1. プロジェクトフォルダへ移動
cd ~/Desktop/myprogram/CSVDate

# 2. 仮想環境を有効化(毎回必要)
source venv/bin/activate

# 3. アプリを起動(ingest.pyは初回のみ)
python app.py

# → ブラウザで http://localhost:7860 を開く

メモリ使用量の目安
コンポーネント 消費メモリ
Qwen2.5:7b(LLM) 約 4.7 GB
nomic-embed-text(埋め込み) 約 0.3 GB
Python + ChromaDB 約 1.0 GB
合計 約 6.0 GB
16GBのうち6GB使用。Safari・VSCode・Terminal を同時に開いても十分余裕があります。

まとめ
今回構築したシステムの特徴:
完全ローカル動作 — インターネット接続不要、データが外に出ない
無料 — APIキー不要、課金なし
高速 — Apple Silicon Metalで最適化されたOllamaで快適な推論速度
拡張しやすい — CSVを差し替えるだけで別のドメインにも応用可能
PokeAPI以外のデータ(例: 自社の商品データ、社内ドキュメントなど)にも同じ構成で応用できます。ingest.py のCSV読み込み部分を変えるだけです。

参考リンク
PokeAPI公式
Ollama公式
LangChain LCEL ドキュメント
ChromaDB ドキュメント
Gradio ドキュメント

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?