はじめに
はじめまして。私は日本工学院八王子専門学校という所で開発をやっています。
現在、AIとの会話アプリをLangChainを用いて開発を行っているのですが、その過程でLangChainのアップデートに対応しようとしてかなり苦労した話を書いていきたいと思います。
RunnableWithMessageHistory
0.2.7で非推奨となったConversationChainに代わる、チャット履歴管理のための仕組みですね。
私が解説するより、以下の記事を読んだ方がいいと思います。
要するに、前と書き方が変わってしまいました。
また、せっかくなのでと同じタイミングでasyncの導入も進めたのが、地獄の始まりでした。。。
DBとの連携がうまくいかない
早めにぶちあたったのがこれです。
もともと既存のDB(SQLite)との連携のために、langchain-communityのSQLChatMessageHistoryクラスを継承したCustomSQLChatMessageHistoryを定義して使っていたのですが、こいつをRunnableWithMessageHistoryに渡すと以下のエラーが頻発するようになりました。
TypeError: 'coroutine' object is not callable
翻訳の通り、コルーチンを関数のように呼び出してはいけないよというエラーになります。
(コルーチンとは: https://zenn.dev/fitness_densuke/articles/understanding_coroutine_in_python)
これがなぜ発生したかというと、CustomSQLChatMessageHistoryクラス内でオーバーライドするasync関数の上に、@propertyデコレータを付けていたからです。
具体的には以下のような感じです
@property
async def aget_messages(self) -> List[BaseMessage]:
オーバーライドする関数は元々プロパティだったのですが、非同期化に伴い別の関数に変更していました。その際に、消し忘れていたのが原因でした。
データベースに会話履歴が書き込まれない
これも最初わけがわかりませんでした。
というのも、
Error in AsyncRootListenersTracer.on_chain_end callback: AttributeError("'dict' object has no attribute 'type'")
というような極めて曖昧なエラーメッセージしか出てこないため、調査に時間がかかったためです。(さりげなさ過ぎて最初スルーしていました)
厄介なのが、このエラーが出ても例外が発生しないため、会話履歴がなぜ保存されないかを探ろうと思ってデバッガを起動しても原因がわかりませんでした。
エラーメッセージをググり、以下の記事を見つけたことで、ようやくパーサが悪さをしていることに気が付きました。
- パースロジックを含む完全なチェーンを定義するのではなく、まずメッセージチェーンを定義する。
- その後にパースロジックを追加する。
私のコードでは以下のようになっていましたので、
chain = chat_prompt | client.ai_client | extract_json
result = await chain_with_history.ainvoke(
{"input": input.text}, {"configurable": {"session_id": current_user.id}}
)
以下のように、出力をjsonとしてパースするextract_jsonをchainの外に出すことで解決しました。
chain = chat_prompt | client.ai_client
result = await chain_with_history.ainvoke(
{"input": input.text}, {"configurable": {"session_id": current_user.id}}
) | extract_json
反省
割と基礎的な知識が欠けていたのと、エラーメッセージでググれば解決する話だったなという印象です。
調査に時間を取られすぎてしまい、必要な機能の実装に時間がかかるのは避けたいところです。
デコレータとか、なんとなく飾り程度に思っていました。(アホ)