CCCMKホールディングス TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

Chainlitを使ってチャットアプリを作ってみました!

Chainlitを使ってチャットアプリを作ってみました!

こんにちは、CCCMKホールディングス TECH LABの三浦です。

先日久しぶりに飛行機に乗りました。当たり前のことなのですが、飛行機を使うと数100キロ離れていてもあっという間に移動することが出来ます。朝起きた場所と、夜眠る場所が数100キロも離れてるなんてなんだか不思議だな、と思いました。

最近以前から試してみたいな、と思いつつ、なかなか試すことが出来ていなかったChainlitというPythonのライブラリをついに試すことが出来ました!Chainlitを使うととてもスピーディーにチャット型のアプリケーションを作ることが出来ます。今回はChainlitを使ってAzure OpenAI Serviceのgpt-3.5-turboやgpt-4を使えるチャットアプリケーションを作ってみました。

Chainlit

Chainlitは対話AIや対話Agentとのチャットアプリケーションをスピーディーに開発することが出来るFrameworkです。

docs.chainlit.io

結構色々な機能が用意されていて、さらにLangChainやAutoGenといった別のLLM関連のFrameworkと機能統合をしています。前回までMulti-Agent ConversationやAutoGenについて色々調べていたのでChainlit+AutoGenの組み合わせは今後ぜひ試していきたいところです。

今回はChainlitの本当に基本的な機能だけを使い、Azure OpanAI Serviceで提供されているChatGPTと対話が出来るチャットアプリケーションの作り方をご紹介します。

基本型

Chainlitを用いた一番シンプルなチャットアプリケーションを作成してみます。具体的には次のようなチャットアプリケーションです。

基本のチャットアプリケーション

基本型ですが、デザインがきれいでとてもいいな、と思っています。このアプリのメインとなるPythonのコードは次のようなとてもシンプルな内容です。

"""
基本形のAzureOpenAIと接続したアプリ
"""
import chainlit as cl

from src.llm_response import generate_message, SYSTEM_CONTENT

# 会話の履歴を格納する変数
message_history = [
    {
        "role":"system",
        "content":SYSTEM_CONTENT
    }
]
@cl.on_message
async def main(message: cl.Message):
    """
    ユーザーからメッセージが送られたら実行される関数
    """

    message_history.append({
        "role":"user",
        "content":message.content
    })

    # Azure OpenAI Serviceと通信を行う
    response = generate_message(message_history)

    # 画面にAzure OpenAI Serviceから受け取った応答を表示する
    await cl.Message(
        content=response["content"]
    ).send()

    message_history.append({
        "role":"assistant",
        "content":response["content"]
    })

chainliton_messageデコレータは、チャットアプリケーションの画面でユーザーがメッセージを送信すると実行される処理を定義します。実行される処理はmain関数にまとめていて、内部ではgenerate_messageからAzure OpenAI ServiceのChatGPTを呼び出してユーザーメッセージへの応答を作成し、画面に表示させています。

generate_messageは別のファイルで定義しています。

import os
from typing import List,Dict

from openai import AzureOpenAI

SYSTEM_CONTENT = """あなたはフレンドリーなアシスタントです。質問に答えて下さい。"""

def generate_message(
        messages: List[Dict],
        model_name: str = "gpt-35-turbo-16k",
        max_tokens: int = 800,
        temperature: float = 0.0
    ):
    
    client = AzureOpenAI(
        api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
        azure_endpoint=os.environ.get("AZURE_OPENAI_API_BASE"),
        api_version="2023-12-01-preview"
    )

    chat_completion = client.chat.completions.create(
        messages=messages,
        model=model_name,
        max_tokens=max_tokens,
        temperature=temperature,
    )
    
    return {
                "role":"assistant",
                "content":chat_completion.choices[0].message.content
    }

chainlitで記述したアプリケーションを起動するには、ターミナルで以下のコマンドを実行します。

chainlit run main.py -w

コマンドにwオプションを指定することで、アプリケーション実行後にソースコードに変更を加えると自動的にその変更内容を読み込み反映してくれるようになります。

デフォルトでは8000ポートでアプリケーションが起動します。ブラウザで"http://localhost:8000/"にアクセスするとアプリケーションを使用することが出来ます。

(少し)発展型

今度は基本型を少しだけ発展させてみます。使用するモデルを切り替えたり、温度パラメータなどを調整出来るような機能を追加してみます。アプリケーションは以下のような画面になります。

いくつか機能を追加しました。

アプリケーションのメインのソースコードは次のようになります。

"""
使用するモデルやパラメータの調整を可能にしたバージョン
"""

import chainlit as cl
from chainlit.input_widget import Slider

from src.llm_response import generate_message, SYSTEM_CONTENT

# 会話の履歴を格納する変数
message_history = [
    {
        "role":"system",
        "content":SYSTEM_CONTENT
    }
]

@cl.set_chat_profiles
async def chat_profile():
    """
    画面の上部に表示されるモデル一覧を設定する
    """
    
    return [
        cl.ChatProfile(
            name="gpt-35-turbo-16k",
            markdown_description="The underlying LLM model is **gpt-35-turbo-16k**.",
            icon="icon画像のURLを指定します。",
        ),
        cl.ChatProfile(
            name="gpt-4",
            markdown_description="The underlying LLM model is **gpt-4**.",
            icon="icon画像のURLを指定します。",
        ),
    ]

@cl.on_chat_start
async def start():
    """
    Chatを開始したタイミングで一度だけ呼ばれる。
    """
    
    # パラメータの設定項目を定義する
    settings = await cl.ChatSettings(
        [
            Slider(
                id="max_tokens",
                label="最大応答",
                initial=800,
                min=1,
                max=4000,
                step=1
            ),
            Slider(
                id="temperature",
                label="温度パパラメータ",
                initial=0,
                min=0,
                max=1,
                step=0.01
            )
        ]
    ).send()

    await update_settings(settings)
    
@cl.on_settings_update
async def update_settings(settings):
    """
    パラメータの設定を変更する。
    """
    cl.user_session.set("llm_parameters",settings)
    
@cl.on_message
async def main(message: cl.Message):
    """
    ユーザーからメッセージが送られたら実行される関数
    """
    llm_parameter = cl.user_session.get("llm_parameters")
    chat_profile = cl.user_session.get("chat_profile")
    message_history.append({
        "role":"user",
        "content":message.content
    })
    
    response = generate_message(
        message_history,
        model_name=chat_profile,
        max_tokens=int(llm_parameter["max_tokens"]),
        temperature=llm_parameter["temperature"]
    )
    
    await cl.Message(
        content=response["content"]
    ).send()
    
    message_history.append({
        "role":"assistant",
        "content":response["content"]
    })

いくつかポイントがあるので、以下にまとめていきます。

Chat Profile

Chainlitでは、チャットアプリケーションの上部にチャットのモード(Chat Profile)を切り替えることが出来る機能を追加することが出来ます。Chat Profileはchainlitset_chat_profilesデコレータで定義することが出来ます。今回は"gpt-35-turbo-16k"と"gpt-4"をChat Profileとして設定しました。

Chainlitではさらに、ユーザーとのチャットセッション間に変数を保持する機能があり、chainlituser_sessionに格納することでセッション間値を保持することが出来ます。ユーザーが画面上でChat Profileを選択すると、user_sessionchat_profileというキーに紐づいて選択したChat Profileのnameが格納されます。

Chat Settings

Chat Settingsを使うことでユーザーがチャット開始時に様々な設定を行うことが出来るようになります。

Chat Settings

設定項目はchainlitChatSettingsを使ってリストで定義します。それぞれの項目には入力用のWidgetを設定することが出来、初期値や設定可能な範囲などをWidgetごとに設定できます。

Chat Settingsの定義はチャット開始時に1度だけ実行される処理の中で行っています。チャット開始時に実行される処理は、chainliton_chat_startデコレータで定義します。また設定が変更された際は設定内容を更新する必要がありますが、Chat Settingsが変更された時に実行したい処理はon_settings_updateデコレータで定義します。設定内容はChat Profileと同様user_sessionに格納するようにしました。

起動時に表示される画面の変更

Chainlitのアプリケーションを起動すると、デフォルトでは以下のような画面が表示されます。

アプリケーションを起動すると最初に表示される画面

実はこの画面に表示している内容は、chainlit runコマンドを実行したディレクトリに自動的に生成される"chainlit.md"というマークダウンに記述された内容です。"chainlit.md"を編集すると、起動時の画面を変更することが出来ます。

たとえば"chainlit.md"を次の様に書き換えます。

# Chatアプリ

Chainlitを使って作ったChatアプリです。

すると起動時の画面はこのようになります。

起動時の画面が変わりました。

まとめ

今回はチャットアプリケーションを作るためのPythonのFramework Chainlitを試してみた話をまとめてみました。デザインがきれいでとてもいいな、と感じました。見た目だけでなく、Chainlitは様々な機能が搭載されているので、それらも使いこなせるようになりたいと思いました。