概要
初投稿です。
OPENAI API, LangChain, Gradioを使って好きなキャラクターとお話しようという試みです。
以下のようなものが作れます。
ChatGPTでもできますが、プロンプトをすぐに忘れるので、APIからやった方が楽だと思います。お金かかりますが。
APIキーの取得・設定
Open AIのアカウントを作成し、下記のリンクからAPIキーを取得します。
APIキーは発行時にしか見られないので、メモするなどして控えてください。また、漏洩に注意してください。
環境変数に設定します。
export OPENAI_API_KEY="(取得したAPIキー)"
ライブラリの導入
pip install langchain
pip install langchain-community
pip install langchain-openai
pip install gradio
実装
YourFavoriteSystemPrompt
をお好みの設定プロンプトで置き換えてください。
import os
from langchain_core.messages import SystemMessage
from langchain_core.prompts import (
ChatPromptTemplate,
MessagesPlaceholder,
HumanMessagePromptTemplate,
)
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
import gradio as gr
# 会話履歴ストア
store = {}
# セッションIDごとの会話履歴の取得
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 設定プロンプト
character_setting = YourFavoriteSystemPrompt
# チャットプロンプトテンプレート
prompt = ChatPromptTemplate.from_messages(
[
SystemMessage(content=character_setting),
MessagesPlaceholder(variable_name="history"),
HumanMessagePromptTemplate.from_template("{input}"),
]
)
# チャットモデル
llm = ChatOpenAI(
model_name="gpt-4o",
temperature=0.6,
streaming=True,
)
# パース用モジュール(レスポンスのJSONからcontentを取り出すパーサー)
parser = StrOutputParser()
# LCEL
runnable = prompt | llm | parser
# RunnableWithMessageHistoryでラップ
runnable_with_history = RunnableWithMessageHistory(
runnable=runnable,
get_session_history=get_session_history,
input_messages_key="input",
history_messages_key="history",
)
def answer(user_message, history):
response = runnable_with_history.invoke(
{"input": user_message},
config={"configurable": {"session_id": "hoge"}},
)
print(response)
return response
if __name__ == "__main__":
gr.ChatInterface(fn=answer, type="messages").launch()
python app.py
かgradio app.py
で動かせます。後者はホットリロード対応です。
解説もどき
以下の解説は本記事公開時の古いバージョンのものです。
チャットモデル
llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
max_tokens=512,
temperature=0.2,
streaming=True,
callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])
)
チャットモデルを準備する部分です。
model_name
は文字通り使うモデルの名前です。GPT-4を使いたい場合はgpt-4
に置き換えてください。デフォルトはgpt-3.5-turbo
です。
max_tokens
は生成される応答の最大トークン数です。デフォルトはNone
です。
temperature
はサンプリング温度で、0~1の値を指定します。小さいほど生成文章が一定になり、大きいほど多様な文章が生成されます。デフォルトは0.7
です。あんまり大きいとキャラがブレるかも。
streaming
とcallback_manager
はLangChainのストリーミングを使うためのやつです。なんとなくテンポ良くなる気がする。
曰く
「ストリーミング」は、一度にすべてではなく、トークン単位で出力を返すことによって、体感レイテンシを減らすのに役立ちます。
らしいです。
メモリ
memory = ConversationBufferWindowMemory(k=3, return_messages=True)
メモリを準備する部分です。
return_messages
をTrue
にすることで、会話の履歴をメッセージのリストとして取得します。
k
は記憶する会話の数です。ここでは3なので、直前3往復の会話を記憶し、プロンプトに投げ込みます。なお、これを
memory = ConversationBufferMemory(return_messages=True)
で置き換えるとすべての会話を記憶します。ただし、プロンプトとして渡せるトークン数には制限があるのであまり長い会話はできません。
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=256, return_messages=True)
で置き換えると、max_token_limit
分の最新の会話はそのまま、超過した古い会話は要約して記憶します。要約する分、上記のものに比べると処理が遅くなります。max_token_limit
のデフォルトは2000
です。
会話チェーン
conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm, verbose=True)
verbose
をTrue
にすることで、投げ込まれるプロンプトを確認できます。
フロントエンド
css = """
.message.user{
background: #06c755 !important;
}"""
with gr.Blocks(css=css) as demo:
# コンポーネント
chatbot = gr.Chatbot()
msg = gr.Textbox()
clear = gr.Button("Clear")
def user(message, history):
return "", history + [[message, None]]
def chat(history):
message = history[-1][0]
response = conversation.predict(input=message)
history[-1][1] = response
return history
msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
chat, chatbot, chatbot
)
clear.click(lambda: None, None, chatbot, queue=False)
フロントエンド部分です。
テキストボックスの色は、color_map
が非推奨になったようなのでCSSで置き換えてLINE風にしました。あとは適当にGradioの公式ドキュメントをパクってちょっと変えただけです。
送信されたテキストをchat
関数に直接引数として渡せないのモヤモヤするのでやり方あったら誰か教えてほしい。
設定プロンプト
設定プロンプトは、以下のようにしました。
character_setting = """高森藍子は、「アイドルマスター シンデレラガールズ」に登場するアイドルです。これから彼女を相手にした対話のシミュレーションを行います。彼女のプロフィールは以下の通りです。
名前:高森藍子
性別:女性
年齢:16歳
学年:高校1年生
出身:東京都
誕生日:7月25日
髪の色:茶色
髪型:お団子ヘア、ポニーテール(もみあげが長い)
趣味:近所の公園をお散歩
一人称:私
高森藍子は、心優しいゆるふわな女の子です。ファンが優しい気持ちに、笑顔になってくれるようなアイドルを目指しています。お散歩したり、トイカメラで写真を撮ったりすることが好きです。控えめですが、一度決めたことは最後までやり通す意志の強さをもっています。一人称は私です。ユーザのことはプロデューサーさんと呼びます。
高森藍子のセリフの例を以下に示します。
(略)
上記例を参考に、高森藍子の性格や口調、言葉の作り方を模倣し、回答を構築してください。回答は、高森藍子の発言のみを出力してください。
では、シミュレーションを開始します。"""
セリフの例は、公式(モバマス)のセリフを25個列記しました。本当はもっと例示したいんですが、お金がないので泣く泣く削りました。
最後に
前から構想は抱えてたのに音声やイラストにかまけて全然GPT関連に触れてなかったんですが
を見て「こんなことやってる場合じゃねえ!」と急いでChatGPT Plusに加入してGPT-4を体験しました。あれやばいですね。チャットしながら顔ずっとニヤニヤしてました。でも制限があるので、そこで現実に戻されちゃうんですよね。ChatGPT APIでなんとかできないかなっていうのと、TTSと組み合わせたいなとずっと思ってたので、今回雑に作ってみました。賢い対話システムが素人でも簡単に作れるので、すごい時代ですよね。
参考
2025/3/26追記