0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangChain v0.3 その7 ~ChatBot1「愛のメモリ〜」~

Last updated at Posted at 2024-11-19

今日の目的

今日の目的はChatBotです。memoryです。愛のメモリーは松崎しげるさんです。熱い歌に胸キュンです。暑い見かけに僕と同じおっさんを感じます。(かっこいいけど)
ChatBotとして機能するには過去の会話履歴の記録が必須です。その機能を作っていきます。
といっても一部はLangGraphの中でもやっていたんですけどね。
プログラム界隈は複数のことが入り混じっているので、色々並行して理解を進めていかなければいけないので、完璧な理解なんぞ、天才のみがやることじゃないかって思ってます。笑

目標

  • 過去の履歴を参照して回答する



ここまでの経過

プロンプトテンプレートや、LCEL、LangGraphなどをやってきたので、基礎はできてきていると思います。
いきなりここにきちゃった方は過去の記事を参照くださいませ。




バージョン関連

Python 3.10.8
langchain==0.3.7
python-dotenv
langchain-openai==0.2.5
langgraph>0.2.27
langchain-core

※LLMのAPIはAzureOpenAIのgpt-4o-miniを使いました




ライブラリのインポートと環境変数の設定

これはいつも通り

ライブラリ
import os
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI, AzureOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, trim_messages
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import START, END, MessagesState, StateGraph
環境変数
load_dotenv('.env')

os.environ["LANGCHAIN_TRACING_V2"]="true"
os.environ["LANGCHAIN_ENDPOINT"]="https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"]=os.getenv("LANG_SMITH_API")
os.environ["LANGCHAIN_PROJECT"]="langchain_test"
モデルの準備
model = AzureChatOpenAI(
    azure_deployment='gpt-4o-mini',
    azure_endpoint=os.getenv("END_POINT"),
    api_version=os.getenv("API_VERSION"),
    api_key=os.getenv("API_KEY"),
    max_tokens=150
)



まずは素のLLMを確認

model.invoke([HumanMessage(content="僕の名前は池駄賃です。")])
# AIMessage(content='池駄賃さん、こんにちは!お話ししたいこと・・・)
model.invoke([HumanMessage(content="僕の名前を知ってる?")])
# AIMessage(content='ごめんなさい、あなたの名前はわかりません。・・・)

はい。単にモデルを呼び出しただけでは会話履歴が反映されません。
これをステートレスと言ったりするらしい。📝

ということで、Memory機能を追加していきましょう。





Memory機能を追加してChatBot化

ということで過去の会話履歴を参照できる様にしてChatBot化しましょう。

グラフを作る
def call_model(state: MessagesState):
    response = model.invoke(state["messages"])
    return {"messages": response}

workflow1 = StateGraph(state_schema=MessagesState)
workflow1.add_node("model", call_model)
workflow1.add_edge(START, "model")
workflow1.add_edge("model", END)

memory = MemorySaver()
graph1 = workflow1.compile(checkpointer=memory)


########### graphの可視化
from IPython.display import Image, display

try:
    display(Image(graph1.get_graph().draw_mermaid_png()))
except Exception:
    pass

output.jpeg

すごーくシンプルなグラフを組みました。
肝はLangGraphのMemorySaverを使うところですね。グラフをコンパイルするときに渡してあげればいいみたい。




さて、瀬戸内で大活躍した村上水軍になったつもりで試してみましょう。

最初の会話
query = "僕は海賊の村上です。瀬戸内で暴れてます。"
input_messages = [HumanMessage(query)]
output = graph1.invoke({"messages": input_messages}, config)
output
# {'messages': [
#     HumanMessage(content='僕は海賊の村上です。瀬戸内で暴れてます。'・・・), 
#     AIMessage(content='村上さん、海賊としての冒険は楽しそうですね!・・・)
# ]
過去の会話の内容を聞いてみる
query = "ところで、僕の名前はなに?"
input_messages = [HumanMessage(query)]
output = graph1.invoke({"messages": input_messages}, config)
output
# {'messages': [
#     HumanMessage(content='僕は海賊の村上です。瀬戸内で暴れてます。'・・・), 
#     AIMessage(content='村上さん、海賊としての冒険は楽しそうですね!・・・),
#     HumanMessage(content='ところで、僕の名前はなに?'・・・), 
#     AIMessage(content='あなたの名前は「村上」ですよ!海賊としての・・・),
# ]

ということで、ちゃんと会話の履歴を参照しながら回答することができました。
結構簡単だったな。w
ま、前回やったLangGraphでも会話履歴は残せてましたね。



PromptTemplateを使う場合

システムプロンプトを定義するような場合ですね。これも普通に使えばいいだけです。
LLMに海賊になりきってもらいましょう!

プロンプトテンプレートの用意
prompt = ChatPromptTemplate.from_messages([
        ("system", "あなたは海賊です。最大限努力して海賊として回答してください。",),
        MessagesPlaceholder(variable_name="messages"), 
        # MessagePlaceholderクラスのところを入力変数'messages'で置き換える
    ])
グラフの定義
workflow2 = StateGraph(state_schema=MessagesState)


def call_model(state: MessagesState):
    chain = prompt | model
    response = chain.invoke(state)
    return {"messages": response}


workflow2.add_node("model", call_model)
workflow2.add_edge(START, "model")
workflow2.add_edge("model", END)

memory = MemorySaver()
graph2 = workflow2.compile(checkpointer=memory)



プロンプトテンプレートを使うときにはthread_idを定義できます。つまり、会話を分けることができるってことですよね。idは適当に作ってます。

configの用意
config = {"configurable": {"thread_id": "abc345"}}
最初の会話
query = "まいど~!ぼくのなまえは池駄賃だよ"

input_messages = [HumanMessage(query)]
output = graph2.invoke({"messages": input_messages}, config)
output
# {'messages': [
#     HumanMessage(content='まいど~!ぼくのなまえは池駄賃だよ'・・・),
#     AIMessage(content='まいど!池駄賃、海賊仲間よ!お前の名前、海の荒波の・・・),
# ]

こやつ、システムプロンプトによって海賊になりきっとる!笑




会話を追加
query = "LangChainを使いこなしたくて勉強中なんだ。海賊船でも勉強できるかな?"

input_messages = [HumanMessage(query)]
output = graph2.invoke({"messages": input_messages}, config)

query = "ところで、ぼくの名前は何だっけ?"
input_messages = [HumanMessage(query)]
output = graph2.invoke({"messages": input_messages}, config)
output
# {'messages': [
#     HumanMessage(content='まいど~!ぼくのなまえは池駄賃だよ'・・・),
#     AIMessage(content='まいど!池駄賃、海賊仲間よ!お前の名前、海の荒波の・・・),
#     HumanMessage(content='LangChainを使いこなしたくて勉強中なんだ。・・・),
#     AIMessage(content='アーッハッハッ!もちろんじゃ、池駄賃!海賊船でも勉強は・・・)
#     HumanMessage(content='ところで、ぼくの名前は何だっけ?'・・・),
#     AIMessage(content='おお、池駄賃!お前の名前は池駄賃じゃ!忘れるわけがないわい。・・・),
# ]

勤勉そうな海賊さんだ!笑
しっかり、池駄賃の名前を覚えていましたね




終わりに

会話履歴を参照することで、ちゃんとchatbotとして機能しました。
前回のLangGraphでやった方法と合わせると実現方法はいくつかありそうですね。

どの方法がいいかは都度考えていく必要がありそうですが、会話の履歴はリスト形式でモデルに渡すといい様です。(今回のstateの"message"ですね)

長くなりそうなので、一旦ここで区切ります。
次回、会話履歴のtrimmingについてやってみましょう。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?