2024/4/16

アプリへのリンク
(Streamlit Community Cloud)

初めて作ったWebアプリです。
OpenAI API(GPT) ~ LangChain の学習の為に作成しました。

開発環境

 WSL2, anaconda, python3.10.12,
 VScode, Chrome,
 Github, Streamlit Community Cloud

使用ライブラリ

 streamlit, langchain, openAI,
 google-api-python-client, wikipedia,
 python-dotenv, GitPython

 

開発メモ

初学者の自分にとっては全てが暗中模索…どちらかと言うとStreamlitの勉強が主というか。
Streamlitは多彩なウィジェットを簡単なコードで実装できて便利そうです。

※この「簡単なコードで実装できる」という開発者の仏心って、実は初心者泣かせだったりします。
 Python標準モジュールなのか外部モジュールなのか、組み込み関数なのかライブラリ特有のインスタンス関数なのか、見分けがつきません。
 Streamlitはひよっこの今から非常に理解しやすい体系ですし、熟練しても使えそうな気がします。(追加機能の開発スピードが速い!)


外部検索のために、LangChain の Agent で Google Custom Search Engine を実装しています。
LangChainはLLMモデル達をラッピングして統一コマンドで制御できるようです。便利。

今後LLMを自在に操るためには、LangChainを覚えるのが最短距離のように思います。
ただ、破壊的アップデートが多すぎ ;_; 追従するだけで息切れします 🙁
モジュール名が長いのでパッと見で怯みがちですが、よく見ると機能そのまんまだったり。


なお、初めてGithubにコミットした時、何も知らずAPI keyを書き込んだ.pyファイルをUPして、瞬く間に OpenAIのAPI keyが凍結・抹消されてしまい驚愕 😮 いい思い出です。



ソースコード

import os
from dotenv import load_dotenv
load_dotenv() # .envに、OpenAI/GoogleのAPIkey, ID 諸々記載

import streamlit as st
from langchain.chat_models import ChatOpenAI
from langchain.agents import AgentType, initialize_agent, load_tools
from langchain.callbacks import StreamlitCallbackHandler
from langchain.memory import ConversationBufferMemory
from langchain.prompts import MessagesPlaceholder


def create_agent_chain(): # ChatモデルとMemoryを定義してAgent初期化
    
    tools = load_tools(["google-search", "wikipedia"])

    # OpenAI Chat Completions API の仕様設定(.envからmodel, temperatureを読込)
    chat = ChatOpenAI(
        model_name=os.environ["OPENAI_API_MODEL"],
        temperature=os.environ["OPENAI_API_TEMPERATURE"],
        streaming=True, # ストリーミング表示
    )
    
    # OpenAI Functions AgentのプロンプトにMemoryの会話履歴を追加するための設定
    agent_kwargs = {
        "extra_prompt_messages": [MessagesPlaceholder(variable_name="memory")],
    }
    
    # OpenAI Functions Agentが使える設定でMemoryを初期化
    memory = ConversationBufferMemory(memory_key="memory", return_messages=True)

    
    return initialize_agent(
        tools,
        chat,
        agent=AgentType.OPENAI_FUNCTIONS, # 安定動作のOpenAI Functions Agent(Function callingに対応したAgent)
        agent_kwargs=agent_kwargs, # いったんデフォルト引数を宣言すると、その後は同語であってもデフォルト宣言が必要
        memory=memory, # 会話履歴を踏まえて応答させるための部品
    )


# 会話履歴を踏まえて応答させるための部品
if "agent_chain" not in st.session_state: # st.session_stateに"agent_chain"がない場合
    st.session_state.agent_chain = create_agent_chain() # st.session_stateを使って一度だけAgentを初期化



st.title("LangChainチャットボット")



# 初期設定
if "messages" not in st.session_state: # st.session_stateに"messages"がない場合
    st.session_state.messages = [] # st.session_state.messagesを空のリストで初期化
    
for message in st.session_state.messages: # st.session_state.messagesでループ
    with st.chat_message(message["role"]): # 役割毎に
        st.markdown(message["content"]) # 保存されているmessage内容をmarkdownとして整形して表示

# userからの入力を受け付ける
prompt = st.chat_input("入力してください")

if prompt: # promptに入力された文字列がある(Noneでも空文字列でもない)場合

    # 会話履歴を残すための部品(userのpromptをst.session_state.messagesに追加)
    st.session_state.messages.append({"role": "user", "content": prompt})
    
    with st.chat_message("user"): # userのアイコン内に
        st.markdown(prompt) # promptをmarkdownとして整形して表示
    
    with st.chat_message("assistant"): # AIアシスタントのアイコン内に
        callback = StreamlitCallbackHandler(st.container())
        agent_chain = create_agent_chain()
        response = st.session_state.agent_chain.run(prompt, callbacks=[callback]) # 会話履歴を踏まえて応答させるためst.session_state.agent_chainを指定
        st.markdown(response) # responseをmarkdownとして整形して表示
    
    # 会話履歴を残すための部品(AIエージェントの応答内容をst.session_state.messagesに追加)
    st.session_state.messages.append({"role": "assistant", "content": response})