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})