AIエージェント

LangChainと検索ツールエージェントとstreamlitとAnthropic
概要
langchainのgithubに、streamlitとlangchainを使用した会話履歴を持つ検索ツール付きエージェントのデモアプリが公開されていた。こちらをAnthropic用に書き換えながら読んでみたため、理解した点や詰まった点をメモしておく。
作ったもの
メモ
msg,memory
msgs = StreamlitChatMessageHistory()
memory = ConversationBufferMemory(chat_memory=msgs, return_messages=True, memory_key="chat_history", output_key="output")
msgはuserとaiの会話履歴で、中間処理は含めない。
memoryはよくわからんが、msgをmemoryにいれて、memoryをAgentExecutorに入れとくことで、AgentExecutorを実行したとき、会話履歴がmsgに追加される。
userメッセージから始める
if len(msgs.messages) == 0 or st.sidebar.button("Reset chat history"):
msgs.clear()
msgs.add_user_message("こんにちは。これから質問しますので日本語で答えてください。")
msgs.add_ai_message("承知しました。何を調べますか?")
st.session_state.intermediate_steps = {}
会話履歴をaiメッセージを先頭に入れるとエラーになるので、無理やりuserメッセージを挟んだ。
StreamlitCallbackHandler, RunnableConfig
st_cb = StreamlitCallbackHandler(st.container(), expand_new_thoughts=False)
cfg = RunnableConfig()
cfg["callbacks"] = [st_cb]
response = executor.invoke(prompt, cfg)
この辺もよくわからん。最終回答の直前に「Complete!」と記載されたステータスコンテナが表示されるのも、ここの処理のためだと思うが、よくわからん。

langchainのメモリについて
概要
メモリクラスが全然理解できていないので、以下を見ながら色々試したメモ。
メモ
とりあえず動かしてみる
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import PromptTemplate
from langchain_anthropic import ChatAnthropic
template = """You are a chatbot having a conversation with a human.
{chat_history}
Human: {human_input}
Chatbot:"""
prompt = PromptTemplate(
input_variables=["chat_history", "human_input"], template=template
)
memory = ConversationBufferMemory(memory_key="chat_history")
llm = ChatAnthropic(model='claude-3-haiku-20240307')
llm_chain = LLMChain(
llm=llm,
prompt=prompt,
verbose=True,
memory=memory,
)
llm_chain.predict(human_input="よろしくね〜")
動いた
> Entering new LLMChain chain...
Prompt after formatting:
You are a chatbot having a conversation with a human.
Human: よろしくね〜
Chatbot:
> Finished chain.
'こんにちは。はい、私はチャットボットです。人間の方と会話させていただくのを楽しみにしています。どのようなお話ができますでしょうか?何か質問やご相談があれば私なりに答えさせていただきますので、気
llm_chain.predictを使用するだけで、会話履歴とかも保存しておいてくれてるようだ。
llm_chain.predict(human_input="例えば?")
> Entering new LLMChain chain...
Prompt after formatting:
You are a chatbot having a conversation with a human.
Human: よろしくね〜
AI: こんにちは。はい、私はチャットボットです。人間の方と会話させていただくのを楽しみにしています。どのようなお話ができますでしょうか?何か質問やご相談があれば私なりに答えさせていただきますので、気軽に話しかけてください。
Human: 例えば?
Chatbot:
> Finished chain.
'では、例えば以下のようなお話題が考えられます:\n\n- 趣味や日常生活について - 読書、映画、料理、旅行など、あなたの好きなことについて教えてください。楽しい会話ができると思います。\n\n- 最近のニュースや世の中の出来事 - 気になるニュースや話題について一緒に意見を交換できます。社会問題など、建設的な議論ができるかもしれません。\n\n- 将来の夢や目標 - あなたの将来の抱負や目標について教えていただけますか?励ましのアドバイスができるかもしれません。\n\n- 好きな有名人について - 俳優、アーティスト、スポーツ選手など、あなたの尊敬する有名人についてお聞かせください。\n\n- 旅行の思い出 - 印象に残る旅行の思い出を共有していただけると楽しいですね。\n\nこれらをはじめ、さまざまな話題で楽しく会話を重ねていきましょう。どのようなお話をしたいですか?'
LLMChainとは?
グラフみてもよくわからん。
print(llm_chain.get_graph().draw_ascii())
# +------------+
# | ChainInput |
# +------------+
# *
# *
# *
# +----------+
# | LLMChain |
# +----------+
# *
# *
# *
# +-------------+
# | ChainOutput |
# +-------------+
[Deprecated] Chain to run queries against LLMs.
非推奨になっていた。今は、LCEL(※prompt | llmみたいなやつ)を使うほうが良いのだろう
LCELで書き直す
LCELで書きたいけど、memoryをpromptにどう入れればいいかわからないので調べてみます。
同じような事を考えている方がいた。
ここ(RunnableWithMessageHistory)を見ると良いらしい。
会話履歴を構造化(ツリー構造とか)して、保存しておくような場合はどうしたらよいだろう?

langchainのメモリについて(その2)
RunnableWithMessageHistoryについてnpakaさんの記事にいいのがあった。
メモ
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
prompt_template = ChatPromptTemplate.from_messages(
[
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
llm = ChatAnthropic(model='claude-3-haiku-20240307')
runnable = prompt_template | llm
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
runnable_with_history = RunnableWithMessageHistory(
runnable,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
res = runnable_with_history.invoke({'input':'よろしく!'}, config={"configurable": {"session_id": "123"}})
ポイント
- プロンプトテンプレートにMessagesPlaceholderを入れる。
- キーがセッションID、値が会話履歴(ChatMessageHistory)の辞書型変数を用意する。
print(runnable_with_history.get_graph().draw_ascii())
# +------------------------+
# | Parallel<history>Input |
# +------------------------+
# *** ***
# *** ***
# ** **
# +------------------------+ +-------------+
# | Lambda(_enter_history) | | Passthrough |
# +------------------------+ +-------------+
# *** ***
# *** ***
# ** **
# +-------------------------+
# | Parallel<history>Output |
# +-------------------------+
# *
# *
# *
# +--------------------+
# | ChatPromptTemplate |
# +--------------------+
# *
# *
# *
# +---------------+
# | ChatAnthropic |
# +---------------+
# *
# *
# *
# +---------------------+
# | ChatAnthropicOutput |
# +---------------------+