Google AI studio ✕ chat bot

Google AI studioくんは、月200万トークンくらいが使えて、お試しには無料で遊べると思うので、遊んでみてください。

import streamlit as st 
import os 
from dotenv import load_dotenv 
import nest_asyncio 

from langchain_community.document_loaders import PyPDFLoader, UnstructuredHTMLLoader 
from langchain.text_splitter import RecursiveCharacterTextSplitter 
from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI 
from langchain_community.vectorstores import FAISS 
from langchain.chains import RetrievalQA 

# Streamlit上でasyncioエラー回避 
nest_asyncio.apply() 

# .envからAPIキー読み込み 
load_dotenv() 
gemini_key = os.getenv("GOOGLE_API_KEY") 
if gemini_key is None: 
st.error(".envに GOOGLE_API_KEY が見つかりません") 
st.stop() 
os.environ["GOOGLE_API_KEY"] = gemini_key 

st.title("📄 PDF / HTML Q&A チャットボット") 

# セッション状態の初期化 
if "messages" not in st.session_state: 
st.session_state.messages = [] 
if "qa_chain" not in st.session_state: 
st.session_state.qa_chain = None 

# 既存メッセージの表示 
for message in st.session_state.messages: 
with st.chat_message(message["role"]): 
st.markdown(message["content"]) 

# ファイルアップロード 
uploaded_file = st.file_uploader( 
"Q&Aを行いたいPDFまたはHTMLファイルをアップロードしてください", 
type=["pdf", "html", "htm"] 
) 

def setup_qa_chain(file_path: str, file_type: str): 
# PDF or HTML の読み込み 
if file_type == "pdf": 
loader = PyPDFLoader(file_path) 
elif file_type in ["html", "htm"]: 
loader = UnstructuredHTMLLoader(file_path) 
else: 
raise ValueError("対応していないファイル形式です") 

documents = loader.load() 
if not documents: 
st.error(f"{file_type.upper()}からテキストを抽出できませんでした。") 
st.stop() 

# テキスト分割 
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) 
texts = text_splitter.split_documents(documents) 

# 埋め込みとベクトルストア 
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001") 
vector_store = FAISS.from_documents(texts, embeddings) 
retriever = vector_store.as_retriever() 

# GeminiモデルとQAチェーン 
model = ChatGoogleGenerativeAI(model="gemini-1.5-flash") 
qa_chain = RetrievalQA.from_chain_type( 
llm=model, retriever=retriever, return_source_documents=True 
) 
return qa_chain 

# ファイルがアップロードされ、QAチェーンが未作成の場合 
if uploaded_file and st.session_state.qa_chain is None: 
with st.spinner("ドキュメントを読み込み中..."): 
try: 
temp_file_path = f"temp_uploaded.{uploaded_file.name.split('.')[-1]}" 
with open(temp_file_path, "wb") as f: 
f.write(uploaded_file.getbuffer()) 

file_type = uploaded_file.name.split(".")[-1].lower() 
st.session_state.qa_chain = setup_qa_chain(temp_file_path, file_type) 
st.success("ドキュメント準備完了!質問を入力してください。") 
os.remove(temp_file_path) 
except Exception as e: 
st.error(f"エラーが発生しました: {e}") 

# QAチェーンが存在する場合にチャット表示 
if st.session_state.qa_chain: 
if prompt := st.chat_input("ドキュメントの内容について質問してください"): 
st.session_state.messages.append({"role": "user", "content": prompt}) 
with st.chat_message("user"): 
st.markdown(prompt) 

with st.spinner("回答を生成中..."): 
try: 
response = st.session_state.qa_chain.invoke({"query": prompt}) 
answer = response['result'] 

st.session_state.messages.append({"role": "assistant", "content": answer}) 
with st.chat_message("assistant"): 
st.markdown(answer) 
except Exception as e: 
st.error(f"質問処理中にエラーが発生しました: {e}")

みたいなのと、.envファイル

GOOGLE_API_KEY = HOGEHOGE;

を同じ階層に書いて、

streamlit run app.pyで実行すると、streamlitの画面が起動して、アップロードしたファイルを呼んで解釈してくれる感じのchat botが作れます。
(事前に

brew install tesseract 
pip install nest_asyncio 
pip install langchain langchain-community pypdf 
pip install pytesseract pillow

が要る。)

## 一応説明

ファイルアップロード

技術: Streamlit st.file_uploader

役割: ユーザーからPDFやHTMLファイルを受け取る
–>
テキスト分割

技術: RecursiveCharacterTextSplitter

役割: 長文をLLMが扱いやすいチャンクに分割
–>
ベクトル化 (Embeddings)

技術: GoogleGenerativeAIEmbeddings

役割: 各テキストチャンクを数値ベクトルに変換
–>
ベクトルストア & 検索

技術: FAISS (FAISS.from_documents + as_retriever)あるいはコレを見てください

役割: 質問時に関連文書を高速に検索できるように格納
–>
QAチェーン作成

技術: ChatGoogleGenerativeAI, RetrievalQA

役割:

検索結果を参照してLLMが自然言語回答を生成

ユーザー質問 → Retriever → LLM → 回答
–>
質問応答

技術: Streamlit st.chat_input + qa_chain.invoke

役割:

ユーザーの質問を受け取り、回答を生成・表示

会話履歴をセッションに保存
てな感じで動いてます。

## 参考・補足

– [ここ](https://github.com/kaisugi/gpt4_vocab_list)に、トーカナイザーで分割された単語が必ず含まれるボキャブラリー表がおいてある。
トーカナイザーについては、[コレ](https://speakerdeck.com/payanotty/tokunaizaru-men?slide=7)とか[コレ](https://dev.classmethod.jp/articles/road-to-llm-advent-calendar-2023-07/)
– HuggingFaceは、機械学習界のgit hubと主張してるプラットフォーム[らしい](https://qiita.com/ski2_1116/items/f74e7b97008663d0702d)
– 下で使ってるlang.chainは[ココ](https://zenn.dev/chips0711/articles/f4ed8ac37eb3a8)とかに載ってるかも。
– Google AI studioでの[ファインチューニング](https://qiita.com/shun_so/items/0f044047553e2fefa66c)について
– [LangChain で社内チャットボット作ってみた](https://zenn.dev/cloud_ace/articles/19bd3554ac8432)
– [RAGチャットボット開発ガイド](https://zenn.dev/daijobu/articles/c1217e40fdf2a5)

### コマンドの意味

– brew install tesseract

意味: Homebrew(macOS用パッケージ管理ツール)を使って Tesseract OCR をインストールする。

Tesseract OCR: 画像内の文字を認識してテキストに変換するツール。

例: スキャンした書類やPDF画像から文字を読み取るのに使う
– pip install pytesseract pillow

意味: 画像処理関連の Python パッケージをインストールする。

pytesseract: Python から Tesseract OCR を呼び出すためのラッパー。(↑とセットで使う。)

Pillow: Python で画像を扱うライブラリ(PIL の後継)。

用途: 画像やPDFから文字を抽出したり、画像を加工したりする。

– pip install langchain langchain-community pypdf

意味: 以下の Python パッケージをまとめてインストールする。

— langchain: LLM(大規模言語モデル)を活用してチェーン処理やアプリ構築を支援するライブラリ。

— langchain-community: LangChain のコミュニティ拡張パッケージ。追加モデルやツールを提供。

— pypdf: PDF ファイルを読み込んだり操作したりできる Python パッケージ。

#### おまけ

# src/scripts/scrape_site.py 
import requests 
from bs4 import BeautifulSoup 
import json 
import os 
import warnings 
from urllib3.exceptions import NotOpenSSLWarning 
from urllib.parse import urljoin, urlparse 

warnings.filterwarnings("ignore", category=NotOpenSSLWarning) 

BASE_URL = "https://www.HOGEFUGA.co.jp/" 
#引っ張ってくるサイトのurl
OUTPUT_DIR = "scraped_site" 
VISITED = set() 

def fetch_text(url): 
"""1ページのテキストを抽出""" 
try: 
headers = { 
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " 
"AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/114.0.0.0 Safari/537.36" } 
resp = requests.get(url, headers=headers) 
resp.raise_for_status() 
soup = BeautifulSoup(resp.text, 'html.parser') 
text_content = "" 
for tag in soup.find_all(['p', 'h1', 'h2', 'h3', 'li', 'span']): 
cleaned = tag.get_text(separator=' ', strip=True) 
if cleaned: 
text_content += cleaned + " " 
return text_content.strip() 
except requests.exceptions.RequestException: 
return None 

def save_text(url, text): 
"""URL に対応したディレクトリ/JSON に保存""" 
parsed = urlparse(url) 
path = parsed.path.strip("/") 

# index ページなら "index.json" に 
if path == "": 
path = "index" 

dir_path = os.path.join(OUTPUT_DIR, os.path.dirname(path)) 
os.makedirs(dir_path, exist_ok=True) 
file_path = os.path.join(OUTPUT_DIR, path + ".json") 

with open(file_path, "w", encoding="utf-8") as f: 
json.dump({"url": url, "text": text}, f, ensure_ascii=False, indent=2) 
print(f"保存: {file_path}") 

def crawl(url, max_pages=50): 
"""再帰的にサイト内リンクをクロール""" 
if url in VISITED or len(VISITED) >= max_pages: 
return 
VISITED.add(url) 

text = fetch_text(url) 
if text: 
save_text(url, text) 

# サイト内リンクを取得して再帰 
headers = { 
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " 
"AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/114.0.0.0 Safari/537.36" } 
try: 
resp = requests.get(url, headers=headers) 
soup = BeautifulSoup(resp.text, 'html.parser') 
for a in soup.find_all('a', href=True): 
link = urljoin(BASE_URL, a['href']) 
if link.startswith(BASE_URL): 
crawl(link, max_pages) 
except requests.exceptions.RequestException: 
pass 

if __name__ == "__main__": 
crawl(BASE_URL) 
print(f"クロール終了。{len(VISITED)} ページ取得")

こういう感じのコード書いて、スクレイピングしてもいいのかも。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です