64
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OPENAI API+LangChain+Gradioでキャラクターとお話するアプリを作る

Last updated at Posted at 2023-04-10

概要

初投稿です。

OPENAI API, LangChain, Gradioを使って好きなキャラクターとお話しようという試みです。
以下のようなものが作れます。
2025-03-26_15h06_45.png
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をお好みの設定プロンプトで置き換えてください。

app.py
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.pygradio 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です。あんまり大きいとキャラがブレるかも。
streamingcallback_managerはLangChainのストリーミングを使うためのやつです。なんとなくテンポ良くなる気がする。

曰く

「ストリーミング」は、一度にすべてではなく、トークン単位で出力を返すことによって、体感レイテンシを減らすのに役立ちます。

らしいです。

メモリ

memory = ConversationBufferWindowMemory(k=3, return_messages=True)

メモリを準備する部分です。
return_messagesTrueにすることで、会話の履歴をメッセージのリストとして取得します。
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)

verboseTrueにすることで、投げ込まれるプロンプトを確認できます。

フロントエンド

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追記

64
65
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
64
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?