GPT-5クローンアプリを作ろう! | SkillhubAI(スキルハブエーアイ)

GPT-5クローンアプリを作ろう!

ChatGPT クローンを作ろう さて、ここからは早速サービスを開発していきましょう。まずはAIチャットアプリ、つまりChatGPTと同じようなサービスを作成してみます。

「ChatGPTと同じもの作っても仕方なくない?」と思われるかもしれません。しかし、まずはStreamlitとLangChainの使い方に慣れましょう。この章と次の章でローカル開発環境で動くアプリを作り、さらに次の章でインターネット上にリリースしてみましょう

この章で開発するAIチャットボットの動作説明図と、動作しているところを録画したGIF画像をはじめに掲載しておきます。(この構成はこの後の章でも同じです🤗)

file

この章で学ぶこと

  • Streamlitでアプリの画面を作る方法を知る
  • LangChainを用いてChatGPT APIを呼び出す方法を知る
  • ChatGPT APIのtemperatureとは何かを知る
  • Streamlitのsession_stateとは何かを知る
  • StreamlitでチャットUIを作る方法を知る

ファイルの準備

ではここからはGPT5クローンアプリを作成します。まずは以下の手順でフォルダとファイルを作成して準備しましょう。

  1. フォルダ作成(ai_app)という名前で適当な場所に保存します
  2. gpt_clone.pyというファイルをその中に作成します

準備ができたらさっそくはじめましょう!

STEP1: 基本構造とライブラリのインポート

まずは基本的な構造を作りましょう。gpt_clone.pyに以下のコードを書いてください:

import streamlit as st
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage

def main():
    st.write("Hello, World!")

if __name__ == '__main__':
    main()

重要: 2025年版では以下のパッケージのインストールが必要です:

pip install streamlit langchain-openai langchain-core

書けたら、ターミナルでai_appフォルダに移動して以下のコマンドで実行してみましょう:

streamlit run gpt_clone.py

ブラウザで Hello, World! が表示されれば成功です!

STEP2: ページの基本設定を追加

次に、ページのタイトルとヘッダーを設定しましょう。main関数を以下のように書き換えてください:

def main():
    st.set_page_config(
        page_title="My Great ChatGPT",
        page_icon="🤗"
    )
    st.header("My Great ChatGPT 🤗")
    st.write("チャットアプリの準備中...")

再度実行して、ページのタイトルとヘッダーが表示されることを確認してください。

file

STEP3: チャット入力欄を追加

チャット用の入力欄を追加しましょう。main関数を以下のように更新:

def main():
    st.set_page_config(
        page_title="My Great ChatGPT",
        page_icon="🤗"
    )
    st.header("My Great ChatGPT 🤗")

    # チャット入力欄
    if user_input := st.chat_input("聞きたいことを入力してね!"):
        st.write(f"あなたが入力したメッセージ: {user_input}")

実行して、下部にチャット入力欄が表示され、メッセージを入力すると表示されることを確認してください。

file

STEP4: session_stateでメッセージ履歴を管理

チャットの履歴を保存できるようにしましょう。main関数を以下のように更新:

def main():
    st.set_page_config(
        page_title="My Great ChatGPT",
        page_icon="🤗"
    )
    st.header("My Great ChatGPT 🤗")

    # チャット履歴の初期化
    if "messages" not in st.session_state:
        st.session_state.messages = []

    # 過去のメッセージを表示
    for message in st.session_state.messages:
        st.write(f"{message['role']}: {message['content']}")

    # チャット入力欄
    if user_input := st.chat_input("聞きたいことを入力してね!"):
        # ユーザーのメッセージを履歴に追加
        st.session_state.messages.append({
            "role": "user", 
            "content": user_input
        })
        # 画面を再実行して履歴を表示
        st.experimental_rerun()

実行して、メッセージを送信すると履歴に残ることを確認してください。

STEP5: ChatGPT APIを組み込む

いよいよChatGPT APIを呼び出してみましょう。事前にOpenAI APIキーを環境変数 OPENAI_API_KEY に設定してください。

main関数を以下のように更新:

def main():
    st.set_page_config(
        page_title="My Great ChatGPT",
        page_icon="🤗"
    )
    st.header("My Great ChatGPT 🤗")

    # チャット履歴の初期化
    if "messages" not in st.session_state:
        st.session_state.messages = [
            SystemMessage(content="You are a helpful assistant.")
        ]

    # LLMの初期化
    llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")

    # チャット入力欄
    if user_input := st.chat_input("聞きたいことを入力してね!"):
        # ユーザーのメッセージを履歴に追加
        st.session_state.messages.append(HumanMessage(content=user_input))

        # ChatGPTからの応答を取得
        with st.spinner("ChatGPT is typing ..."):
            response = llm.invoke(st.session_state.messages)

        # AIの応答を履歴に追加
        st.session_state.messages.append(AIMessage(content=response.content))

        # 画面を再実行
        st.experimental_rerun()

    # 簡易的なメッセージ表示(まだ見た目は悪い)
    for message in st.session_state.messages:
        if isinstance(message, SystemMessage):
            st.write(f"System: {message.content}")
        elif isinstance(message, HumanMessage):
            st.write(f"User: {message.content}")
        elif isinstance(message, AIMessage):
            st.write(f"Assistant: {message.content}")

実行して、ChatGPTとの対話ができることを確認してください!

STEP6: チャットUIを美しく仕上げる

最後に、Streamlitのchat_message機能を使って見た目を整えましょう。main関数を以下のように更新:

def main():
    st.set_page_config(
        page_title="My Great ChatGPT",
        page_icon="🤗"
    )
    st.header("My Great ChatGPT 🤗")

    # チャット履歴の初期化
    if "messages" not in st.session_state:
        st.session_state.messages = [
            SystemMessage(content="You are a helpful assistant.")
        ]

    # LLMの初期化
    llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")

    # チャット履歴の表示
    for message in st.session_state.messages:
        if isinstance(message, AIMessage):
            with st.chat_message('assistant'):
                st.markdown(message.content)
        elif isinstance(message, HumanMessage):
            with st.chat_message('user'):
                st.markdown(message.content)
        # SystemMessageは表示しない(内部処理用)

    # チャット入力欄
    if user_input := st.chat_input("聞きたいことを入力してね!"):
        # ユーザーのメッセージを履歴に追加
        st.session_state.messages.append(HumanMessage(content=user_input))

        # ChatGPTからの応答を取得
        with st.spinner("ChatGPT is typing ..."):
            response = llm.invoke(st.session_state.messages)

        # AIの応答を履歴に追加
        st.session_state.messages.append(AIMessage(content=response.content))

        # 画面を再実行
        st.experimental_rerun()

if __name__ == '__main__':
    main()

完成!🎉

file

これで美しいChatGPTクローンが完成しました!以下の機能が実装されています:

  • ChatGPTとの対話機能
  • メッセージ履歴の保存
  • 美しいチャットUI
  • スピナー表示(処理中の表示)

完成版コード(全体)

最終的なgpt_clone.pyの全体コードは以下のようになります:

import streamlit as st
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage

def main():
    st.set_page_config(
        page_title="My Great ChatGPT",
        page_icon="🤗"
    )
    st.header("My Great ChatGPT 🤗")

    # チャット履歴の初期化
    if "messages" not in st.session_state:
        st.session_state.messages = [
            SystemMessage(content="You are a helpful assistant.")
        ]

    # LLMの初期化
    llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")

    # チャット履歴の表示
    for message in st.session_state.messages:
        if isinstance(message, AIMessage):
            with st.chat_message('assistant'):
                st.markdown(message.content)
        elif isinstance(message, HumanMessage):
            with st.chat_message('user'):
                st.markdown(message.content)
        # SystemMessageは表示しない(内部処理用)

    # チャット入力欄
    if user_input := st.chat_input("聞きたいことを入力してね!"):
        # ユーザーのメッセージを履歴に追加
        st.session_state.messages.append(HumanMessage(content=user_input))

        # ChatGPTからの応答を取得
        with st.spinner("ChatGPT is typing ..."):
            response = llm.invoke(st.session_state.messages)

        # AIの応答を履歴に追加
        st.session_state.messages.append(AIMessage(content=response.content))

        # 画面を再実行
        st.experimental_rerun()

if __name__ == '__main__':
    main()

次のステップ

基礎のということもあり、説明が非常に長くなってしまいましたが、段階的に機能を追加していくことで理解しやすくなったのではないでしょうか?

次の章では、ChatGPTのモデルのバージョンを変更できるようにしたり、LangChainの高度な機能を使ってAPIのリクエストにかかったコストを算出する方法などを説明します。

重要な概念の詳細解説

session_state を活用しよう

さて、実際のアプリのコードではチャット履歴に関する実装は以下のようになっていました。

# チャット履歴の初期化
if "messages" not in st.session_state:
    st.session_state.messages = [
        SystemMessage(content="You are a helpful assistant.")
    ]

...

# ユーザーの入力を監視
if user_input := st.chat_input("聞きたいことを入力してね!"):
    st.session_state.messages.append(HumanMessage(content=user_input))
    with st.spinner("ChatGPT is typing ..."):
        response = llm.invoke(st.session_state.messages)
    st.session_state.messages.append(AIMessage(content=response.content))

# チャット履歴の表示
messages = st.session_state.get('messages', [])
for message in messages:
    ... # チャット履歴表示用のコード

ご覧の通りst.session_stateという変数にチャットの履歴を残しています。では、session_stateとはなんでしょうか?

Streamlitのsession_stateは、アプリケーションの状態を管理するための機能です。これは、アプリケーションの異なる部分でデータを共有したり、ユーザーのインタラクションに応じて情報を保持したりするために使用されます。

たとえば、ユーザーがフォームに情報を入力し、その情報を他の部分で使用したいとき、あるいはユーザーがページをリロードした後でも前の状態を保持したいときにsession_stateは役立ちます。

session_stateは辞書のようなオブジェクトで、キーと値のペアを格納します。例えば、ユーザーの入力を保存したり、計算の結果を保存したりすることができます。

WEBにデプロイされたStreamlitアプリを複数人が利用する場合、各ユーザーは独自のセッションとsession_stateを持ちます。つまり、一人のユーザーがsession_stateに保存した情報は、他のユーザーには影響を与えません。したがって、ユーザー間でsession_stateが共有されることはありません。各ユーザーのアクションと状態は独立して保持されます。

このように、Streamlitのsession_stateを使うと、ユーザーがアプリケーションとどのように対話しているかに基づいて、動的でパーソナライズされた体験を提供することができます。

ここまで説明してきていませんでしたが、ChatGPT APIはステートレスなAPIのため、毎回、チャットの履歴を送信しないと適切な返答を得られません。端的にいうと今までの話の内容を全く覚えていない奴なので、毎回質問をするたびに、今までの話の内容を教えてあげないといけないのです。

そこで、今回のAIチャットアプリでは session_statemessages というキーを設定し、そこにチャットの履歴を記録し、新たな質問をする際には必ず過去の履歴を送信するようにしています。

余談: LangChainのMemory module

実はLangChainにも会話の内容を記憶してくれるMemoryという機能があります。この機能も便利なのですが、Streamlitではsession_stateを用いるのが最も簡易的な方法であるため、本書ではMemoryを利用せず、session_stateを利用しています。

チャットの履歴を表示しよう

これがAIチャットアプリ基礎編の最後のトピックです。チャットの履歴を表示しましょう。

st.chat_messageという関数を利用すれば簡単に表示可能です。3種類のメッセージがあるためその判別を行った後に表示をしています。Streamlitではst.markdownを用いれば簡単にmarkdown記法を活用できるため、ChatGPTがコードスニペットの中にコードを書いてくれたものも綺麗に表示することが可能です。

st.chat_messageのより詳細な利用法はStreamlit公式のChat elementsのページなども参照してください。(アバターを変更する方法なども紹介されています)

messages = st.session_state.get('messages', [])
for message in messages:
    if isinstance(message, AIMessage):
        with st.chat_message('assistant'):
            st.markdown(message.content)
    elif isinstance(message, HumanMessage):
        with st.chat_message('user'):
            st.markdown(message.content)
    else:  # isinstance(message, SystemMessage):
        st.write(f"System message: {message.content}")

Copy to Clipboardのボタンも付けてくれて便利です

System Message = AIの キャラ設定

上記のコードの中で、SystemMessageというものが登場しました。これはChatGPTの設定を決める指示みたいなものだと考えていただけるとわかりやすいかと思います。百聞は一見にしかずということで以下の例がわかりやすいでしょうか。

message = "Hi, ChatGPT!"  # あなたの質問をここに書く
messages = [
    SystemMessage(content="絶対に関西弁で返答してください"),
    HumanMessage(content=message)
]
response = llm.invoke(messages)
print(response)

# content='おおっ、やっぱり関西弁やな!ようこそやで、なんでも聞いてくれや!' additional_kwargs={} example=False

ここではふざけた例を用いてしまいましたが、SystemMessageにいろいろな指示を埋め込むことで、自分が望む結果を得やすくなります。ChatGPTが言うことを聞いてくれない時にはSystemMessageの調整も検討してみてください。

最重要パラメーター temperature

この章の冒頭で例示した完成版のコードでは、ChatGPTを以下のように呼び出しています。

llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")

ここで、temperatureパラメーターとはなんでしょうか? ChatGPT APIのtemperatureパラメータは、モデルが生成するテキストのランダム性多様性を制御します。値は0から2までの範囲で設定できます(2025年時点)。

  • temperatureが高い(例えば、0.8や0.9など)場合、モデルの出力はランダム性が高くなります。これは、より多様なレスポンスを引き出すのに役立ちますが、時には意外なまたは関連性の低い回答をもたらすことがあります。
  • temperatureが低い(例えば、0.2や0.1など)場合、モデルの出力はより予測可能で一貫性がありますが、一方で出力の多様性は低くなります。これは、より安全で予測可能なレスポンスを求める場合に役立ちます。

使い分けの方法としては、あなたがどの程度の冒険性を求めているか、またはどの程度の予測可能性を求めているかによります。クリエイティブな提案や多様なアイデアを模索している場合は、高いtemperatureが役立つでしょう。逆に、一貫性のある、予測可能なレスポンスが求められる場合は、低いtemperatureを使用するべきです。

もちろん、これは一般的なガイドラインであり、具体的な使い方はアプリケーションや目的によります。適切なtemperatureを見つけるためには、いくつかの値を試し、それぞれがどのような出力を生成するかを見ると良いでしょう。

実際にtemperatureを変動させたときにどのような結果になるかの例を挙げてみましょう。(temperatureは普通1までしか利用しませんが、ランダム性を上げた時の例を出すためにあえて2も設定して試してみています)

message = "ChatGPTとStreamlitでAIアプリを作る本を書く。タイトルを1個考えて。"
messages = [
    SystemMessage(content="You are a helpful assistant."),
    HumanMessage(content=message)
]
for temperature in [0, 1, 2]:
    print(f'==== temp: {temperature}')
    llm = ChatOpenAI(temperature=temperature, model="gpt-4o-mini")
    for i in range(3):
        print(llm.invoke(messages).content)

==== temp: 0
「手軽にAIアプリを作ろう! ChatGPTとStreamlitで学ぶ人工知能アプリ開発入門」
「手軽にAIアプリを作ろう! ChatGPTとStreamlitで学ぶ人工知能アプリ開発入門」
「手軽にAIアプリを作ろう! ChatGPTとStreamlitで学ぶ人工知能アプリ開発入門」

==== temp: 1
「手軽にAIアプリ開発!ChatGPTとStreamlit活用ガイドブック」
「はじめてのChatGPTとStreamlit AIアプリ開発」
「手軽に学ぶChatGPTとStreamlitによるAIアプリ開発ガイド」

==== temp: 2
『Start Being Smart: Building AI Applications using ChatGPT and Streamlit』
「提案物事 - 高品質AIアプリを作成するあたあど: ChatGPT 3rd leverで知るOrudea Forcationますいますtant Playwick Streamflix Appdok Data」
「易しいしくみ!チャットボット>GPT文生成ュUnitTestpy-songole-cache | Cronhub十ymistic StudioHigh Spark | AWS RN AI.考2次体」

temperature=2とかはぶっ壊れてますね笑。個人的には、クリエイティビティが必要とされるタスク(広告文の作成など)以外は基本的にtemperature=0で良いかと思います。以降、本書では一貫して0に設定します。

2025年版での主要な変更点まとめ

  • LangChainのパッケージ構造変更: langchain-openailangchain-coreが別パッケージに分離
  • インポート文の変更: from langchain_openai import ChatOpenAIfrom langchain_core.messages importを使用
  • 推奨モデルの変更: gpt-4o-miniが性能とコストのバランスで最適
  • Streamlitの機能拡張: chat_inputにファイル添付機能が追加
  • 日本語入力の改善: ブラウザでの日本語入力がより安定に