4
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?

Bedrockを使ったチャットボットを作って気がついた課題をLangChainで実装してみた

Posted at

1. はじめに

 ここではLangChainを使ったチャットボット with RAG and MCPサーバについて説明を書きます。LangChainはLLMを活用したアプリケーション開発を簡単に実装するためのフレームワークですが、メリットの一つに非常に柔軟性が高いことが挙げられます。以前の私の記事でBedrockを使ってヘルプデスク用のチャットボットを作成しましたが、以下のような課題が明らかとなりました。
 (1) 添付画像付きの質問に回答できるように実装することは困難
 (2) MCPサーバへの接続が煩雑で難しい、MCPクライアントのコーディングも必要
 (3) 詳細なログ(例えば、LLMに渡すプロンプトの中身やLLMが返した応答、MCPサーバからの応答、など)をサクッと簡単に出力できない

 (2)と(3)は煩雑とはいえ頑張ればなんとかなりますが、(1)は非常に困難です。その理由は、まず、teamsとエージェントを繋ぐQ Developerに画像を渡す仕組みがありません。また、コンソールのエージェントのテストウィンドウで添付画像を付けて直接エージェントに画像を渡してもエージェントはエラーになって失敗します。ClaudeはJPEGファイルを解析できる能力があるにも関わらずエージェントとClaudeで正常にやり取りできないようです。従ってコンソールを使わずにpythonプログラムなどで画像受け渡しをコーディングしBedrockのAPIを呼ぶようにコーディングすれば可能になりそうですが、もはやBedrockのメリットである安全で簡単に実現可能のメリットはありません。 そこで、LangChainを使って同様なチャットボットを構築した際、上記難点は改善されるのかどうか検討した内容を説明します。

2. LangChainとは

 LangChainはLLMと外部データやツールと有機的に組み合わせて高度な機能を実現するためのオープンソースのフレームワークです。BedrockはLLMなどの基盤モデルを安全で簡単に利用することができるサービスですが、LangChainはLLMを利用するだけでなく複雑なアプリケーションを柔軟に開発することができます。BedrockではBedrock上で利用できるように設定された基盤モデルしか利用できませんが、LangChainはAWS, Azure, Google、さらにはOSSも利用可能です。逆にBedrockと比較してディメリットは難易度が高くバグが入り込みやすいため開発者の責任が大きいと思います。

3. チャットボットのアーキテクチャの検討

 基本的な構成は前回Bedrockで作成したチャットボットと同様に、RAGとMCPサーバを使って質問に対する回答を生成しますが、違いは文章だけの質問だけでなく添付画像付きの質問にも回答できるように実装しました。つまり、課題(1)を積極的に対処しました。LLMは最初はOSSのLlamaをローカルマシンで実行していましたが、最終的にはBedrockのClaude Haiku 4.5を使いました。ベクトルデータベースはOSSであるChromaを使いました。

3.1 質問の例

 ユーザは次のような質問文に添付ファイルを付けて質問することを想定します。

質問文:このエラーの原因と解決策を教えてください。

jpeg.jpeg

 添付ファイルはネットから拾ってきた画像です。ワードのファイルを開こうとしたらエラーが出て正常に開けませんでした。この画像にはたくさんの文章が書いてありますが、エラーメッセージである「We’re sorry, but Word has run into an error opening this document.」が最も重要な文章です。

3.2 最初に考えついたアーキテクチャAの検討

 私が最初に考えたアーキテクチャは以下の図の通りです。アーキテクチャAと呼びます。

アーキテクチャA.001.jpeg

 質問文に関連する情報をナレッジベースとMCPサーバに問い合わせて得られた検索結果と添付画像を基にして質問に対する回答をClaudeに作成してもらいます。

3.3 アーキテクチャAの問題点

 アーキテクチャAをLangChainを使ってプログラムを作成し実行しました。すると、あまり良い回答が得られないことに気が付きました。つまり、3.1の例ですと、ワードには関係無い回答をしてくるのです。
 この原因はアーキテクチャAの図を良く見ると気が付きました。ナレッジベースの検索およびMCPサーバの検索には添付画像の情報は一切使っていません。つまり、「このエラーの原因と解決策を教えてください。」という文章のみしか入力として与えていません。すると、エラーに関する情報を検索すれば良い事は分かるが、何のエラーなのかはこの文章だけでは分かりません。すると、関連性の薄い情報がたくさん得られることになります。

3.4 エラーメッセージを抜き出す方法

 3.3の問題点を解決するためには、まずは添付画像のエラーメッセージを抜き出したいです。ならならば、エラーメッセージである「We’re sorry, but Word has run into an error opening this document.」という文章を見れば、ワードのエラーだと分かります。また、ワードファイルをオープンしようとした時にエラーが発生したことも分かります。そこで、OCRの機能が必要かと思いました。
 ただし、添付画像を見てみると、このエラーメッセージ以外にもたくさんの文章があります。このファイルに書かれている文章はエラー解析には必要のない文章です。そこで、単純なOCRでは不十分です。つまり、文章の中から特に重要な文章、エラーメッセージを表す文章、を抜き出す賢いOCRが必要です。
 ここでは、賢いOCRとして生成AIを使います。

賢いOCRとしてのLLM.011.jpeg

Claudeに入力するシステムプロンプトとユーザテキストは以下の通りです。

 system_prompt="画像の内容を簡潔に説明してください。特にエラーメッセージを抽出してください。"
 
 user_text="画像を解析してください。"

Claudeから得られた出力は以下の通りになりました。

--- ここから ---

画像解析結果

エラーメッセージ

Microsoft Word エラーダイアログ

We're sorry, but Word has run into an error opening this document.

内容説明

Microsoft Wordがドキュメントを開く際にエラーが発生したことを示すダイアログボックスです。

--- ここまで ---

単なるOCR以上の仕事をしてくれました。最も重要なエラーメッセージを抜き出してくれています。そして、このエラーについての補足説明も付けてくれました。

3.5 エラーメッセージと元の質問を統合する方法

 次に、画像から抽出した情報を使って適切な質問文を生成したいです。元の質問文では「このエラー」と書いてありましたが、「このエラー」とは画像の中のエラーメッセージのことです。うまく組み合わせて自然な質問文を生成するために、再びClaudeを利用します。

統合質問文を生成するLLM.001.jpeg

Claudeに入力するシステムプロンプトとユーザテキストは以下の通りです。

system_prompt = (
    "あなたは日本語の校正アシスタントです。"
    "意味を変えず、簡潔で自然な日本語に整えてください。"
    "可能なら、画像から抽出したメッセージを最初の一文に自然に取り込み、"
    "丁寧で読みやすい文にしてください。"
    "文意の追加・削除・脚色は行わないでください。"
)
user_text = (
    "次の情報を統合して、自然で簡潔な日本語の『質問文』にしてください。\n\n"
    "【抽出メッセージ】\n" + msg + "\n\n"
    "【元の質問文】\n" + oq + "\n\n"
    "制約:\n"
    "1) 意味を変えない。\n"
    "2) 冗長な重複を避ける。\n"
    "3) 丁寧だが過度に長くしない。\n"
    "4) 句読点と引用符(「」)を自然に用いる。\n"
    "5) 1〜2文でまとめる。\n"
    "6) 出力は統合済みの質問文のみ。説明や前置きは不要。"
)

ここで、msg には画像から抽出したエラーメッセージが入っており、oq は元の質問文が入っています。

Claudeから得られた出力は以下の通りになりました。

--- ここから ---

Microsoft Wordでドキュメントを開く際に「We're sorry, but Word has run into an error opening this document.」というエラーが表示されるのですが、この原因と解決策を教えてください。

--- ここまで ---

一行で自然な分かりやすい質問になりました。以降、上記生成された文章を「統合した質問」と呼びます。

3.6 ナレッジベースにアクセスする方法

 helpdesk.csvファイルの中身をベクトル化してOSSのChromaにストアします。Chromaは統合した質問を受けて関連する情報を返します。

3.7 MCPサーバにアクセスする方法

 Microsoft のサービスなどの情報を取得するためにMicrosoftが公開しているMCPを利用します。MCPとはModel Context Protocolのことで、LLMが外部ツールやデータソースとやり取りするための標準プロトコルです。マイクロソフト製品やサービスに関するチャットボットを作るという目的を実現するためにはこれを利用します。
 LangChainのエージェントはいわゆるReActという機能を持っていてLLMを使って推論(Reasoning)と行動(Acting)を繰り返し実行して複雑な問題を柔軟に解決します。つまり、エージェントは統合した質問文とシステムプロンプトとMCPサーバのツールの情報をLLMに渡して次にどうすべきかをLLMに問います。LLMは統合した質問文からMCPサーバに問い合わせるべきかどうかを判断し問い合わせるならばどのツールを使うべきかを判断します。そして、使うべきツールを決定するとそのツールを使う際に必要となる情報を生成しエージェントへ返します。エージェントはこの情報を使ってMCPサーバへリクエストを送信します。エージェントはMCPサーバから応答を受け取ると、再度、LLMにこの応答を渡して、LLMは次にどうすべきかを判断します。LLMは再度MCPサーバに問い合わせるべきと判断した場合は上記と同様使うべきツールとそのツールに必要な情報をエージェントへ返します。これ以上MCPサーバに問い合わせないと判断した場合は、エージェントから渡されたシステムプロンプトの指示に従って応答を整形してエージェントへ渡します。エージェントはその整形された応答を呼び出し元にリターンします。

アドベントカレンダー(bedrock)の図.001.jpeg

 システムプロンプトは以下の通りです。ツールはmicrosoft_docs_searchを使って統合した質問文に関するメタデータを取得した後、microsoft_docs_fetchを使って統合した質問文に関する詳細な情報を取得します。最後に、 {title,url,content}のJSONフォーマットに整形して回答として返します。整形する理由は、生データには余計なHTMLタグやメタ情報などが含まれており、これらの余計な情報を削除するためです。

system = (
           "You are a retrieval agent for Microsoft Learn docs. "
           "Use microsoft_docs_search then microsoft_docs_fetch. "
           "Return ONLY a JSON array of {title,url,content} with no commentary. If you cannot find relevant docs, return [] and nothing else."
)

 以下はLLMが整形して生成した回答の一部です。

{"time": "2025-12-10T16:29:01+0900", "level": "INFO", "name": "rag_demo", "message": "MCP raw", "preview": "```json\n[\n  {\n    \"title\": \"You receive an error message when you try to open an Office document\",\n    \"url\": \"https://learn.microsoft.com/en-us/troubleshoot/microsoft-365-apps/office-suite-issues/error-open-document\",\n    \"content\": \"# You receive an error message when you try to open an Office document\\n\\n## Symptoms\\n\\nWhen you try to open a file in any Microsoft Office program, you may receive error messages such as:\\n- Filename is not valid\\n- The file could not be accessed\\n- The path you "}

統合した質問に対して解決のために参考となる情報をMCPサーバが返し、LLMが title, url, content に整理してJSON形式の応答を生成しています。

3.8 最後に質問に対する回答を生成する方法

 最後にナレッジベースの検索結果とMCPサーバからの整形された応答をLLMに渡して統合された質問文に対する回答を生成してもらいます。

最後の回答を作るLLM.001.jpeg

3.9 全体の処理

 以上の検討から作成したプログラムのフロー図は以下の通りです。これをアーキテクチャBと呼ぶことにします。

フロー図.001.jpeg

 まず、CSVファイルからナレッジベースを作成します。次に、質問に添付画像があるかどうかを検証します。添付画像がある場合は、Claudeを使って添付画像からエラーメッセージを抜き出し(3.4)、Claudeを使ってエラーメッセージと元の質問文を統合し(3.5)、統合した質問文でナレッジベースを検索し(3.6)、Claudeを利用するエージェントは統合した質問文でMCPサーバを検索し(3.7)、最後にClaudeはナレッジベースの検索結果とMCPサーバの検索結果を使って統合した質問文の回答を生成します(3.8)。
 添付画像が無い質問の場合は、質問文に関するナレッジベースの検索をして、質問文に関連するMCPサーバの検索をして、それらを使ってClaudeは質問に対する回答を生成します。
 

3.10 ログの出力

 ログはpythonの標準ライブラリであるloggingを利用しました。以下のコードは一例です。

    logger.info(
        "LLM response (text)",
        extra={"extra_json": {"output_len": len(text), "preview": text[:200]}},
    )

ログを出力したい場所にloggerを記述します。上記はログレベルがINFOで “LLM response (text)” というラベルを付けて出力します。以下がこのloggerで出力したログの例です。

{"time": "2025-12-10T16:28:15+0900", "level": "INFO", "name": "rag_demo", "message": "LLM response (text)", "output_len": 128, "preview": "Microsoft Wordでドキュメントを開く際に「We're sorry, but Word has run into an error opening this document.」というエラーが表示されるのですが、この原因と解決策を教えてください。"}

3.11 実行結果

 実行結果を以下に示します。

--- ここから ---

(venv) mishima@Mac langchain-advent % python3 rag_mcp_logging_advent31.py --question "このエラーの原因と解決策を教えてください。" --image screenshots/word-error.jpeg

Microsoft Wordドキュメント開封エラーの対処方法

ご質問ありがとうございます。提供いただいた情報に基づいて、段階的な対処方法をご案内いたします。

【第1段階】基本的な確認事項

  1. ファイルのアクセス権限確認

    • ドキュメントのプロパティを確認し、読み取り専用になっていないか確認
    • ファイルが暗号化されていないか確認(Windows Explorerで「E」マークの有無)
  2. ディスク容量の確認

    • C:ドライブの空き容量が十分か確認(最低1GB以上推奨)
    • メモリ使用率を確認
  3. ファイルパス長の確認

    • ファイルパスが259文字以内か確認
    • 長い場合は、より短いパスに移動してから開く

【第2段階】Word固有の対処方法

  1. 「開く」と「修復」機能を使用

    • Word起動 → ファイル → 開く → 対象ファイルを右クリック → 「開いて修復」を選択
  2. テキスト回復コンバーターを使用

    • ファイル → 開く → ファイルの種類を「テキスト回復コンバーター」に変更
  3. ファイル形式の変更

    • ファイルを別の形式(.docx → .doc など)で保存し直す

【第3段階】システムレベルの対処方法

  1. クリーンブート環境での動作確認

    • 不要なスタートアップアプリを無効化して再起動
    • 常駐ソフトウェアの干渉を排除
  2. 別ネットワークでの再現性確認

    • 社内ネットワークを経由しないネットワークで試行
  3. 別Windowsプロファイルでの再現性確認

    • 別のユーザーアカウントでログインして試行

【第4段階】Office再インストール

上記で改善しない場合:


ご不明な点やエラーが改善しない場合は、以下の情報をお知らせください:

  • エラーメッセージの全文とエラーコード
  • 問題が発生するファイルの保存場所
  • 上記で試行済みの対処方法

ご不便をおかけして申し訳ございません。ご協力よろしくお願いいたします。
(venv) mishima@Mac langchain-advent %

--- ここまで ---

4. 考察

4.1 三つの課題は解決したか?

 「1.はじめに」で説明しましたが、Bedrockだけでヘルプデスク用チャットボットを作った際には以下の課題が見つかりました。
 (1) 添付画像付きの質問に回答できるように実装することは困難
 (2) MCPサーバへの接続が煩雑で難しい
 (3) 詳細なログ(例えば、LLMに渡すプロンプトの中身やLLMが返した応答、MCPサーバからの応答、など)をサクッと簡単に出力できない
これらの課題が解決したかを考えます。
 まず、(3)ですが、3.10で説明したように、loggerを使って簡単に出力できました。
 次に(1)ですが、3.4から3.9、および実行結果を3.11で説明しましたように解決できました。
 最後に(2)のMCPサーバへの接続について説明したいと思います。LangChainのエージェントはMCPサーバと直接やり取りをすることができます。従って、コードは以下だけです。

   agent = create_agent(bedrock_chat, tools)
    result = await agent.ainvoke({
        "messages": [
            {"role": "system", "content": system},
            {"role": "user", "content": f"Question: {question}\nMax docs: {max_docs}"},
        ]
    })

bedrock_chatはClaudeのオブジェクト、toolsはMCPサーバが提供しているツール群を引数にとってエージェントのオブジェクトを作ります。次に、systemはシステムプロンプト、questionは統合した質問、max_docsは最大で幾つのドキュメントを取得するか、を引数にして、エージェントを実行します。MCPサーバからの応答が整形されて result に格納されます。たったのこれだけで済みました。
 BedrockだけでMCPサーバとやり取りする前回の実装ではBedrockのエージェントはMCPサーバと直接やり取りする機能を持っていません。代わりにどのActionGroupを使うかを選択する機能しか有せず、ActionGroupの中身のLambda関数でpython言語を使ってMCPサーバとのやり取り、つまりはMCPクライアントをゼロから書かなければならないです。このプログラムはActionGroupとの入出力インターフェースに合わせたコーディングやMCPの生のパケット、つまり、JSON‑RPCパケットを自前で書く必要があります。これはなかなか大変です。Lambda関数は約200行になりました。以上から、LangChainの方がMCPサーバと簡単にやり取りできることが分かりました。

4.2 エージェントのReActをもっと活用したアーキテクチャ

 エージェントのReActをもっと活用したアーキテクチャも考えられます。

まるっとエージェントのアーキテクチャ.001.jpeg

そして、3.9で説明した処理のフローをシステムプロンプトに記述します。

システムプロンプト:添付画像がなかったら質問に関連する情報をナレッジベースから検索してMCPサーバも検索して最後にそれらの情報を使って質問に対する回答を書いてください。添付画像があった場合はまずは添付画像から重要な文章、特にエラーメッセージを取り出してください。次に元の質問とその取り出したメッセージを統合して自然な質問の文章を生成してください。次にその統合した質問に関する情報をナレッジベースから取り出してください。また、MCPサーバからもその統合した質問に関する情報を取り出してください。最後にそれらの情報を使って質問に対する回答を生成してください。

このアーキテクチャのメリットは細かい制御はエージェントに全部任せてしまい、プログラムがスッキリすることです。しかし、以下のような課題を検討する必要がありそうです。

(a) 問題があった場合、デバッグが難しい
どういう順序で処理が行われてどこで問題が発生したかが分かりにくい。LLMの思考過程は見えません。また、再現試験が難しい。同じ入力を与えてもLLMは完全に同じ処理を実行してくれる保証は無い。
(b) 安全性、データガバナンス、コンプライアンス
機密性の高い情報を扱う場合、エージェントがどこでどう使うかの制限が難しい
(c) プロンプトの限界
どんなに厳密に指示を書いたとしてもツール選択の誤りなどは完全には防げない。誤りがあった場合にどう振る舞うかを規定するのも難しい。
(d) 依存性解決が困難になるかも
パッケージのアップグレードなどを行う場合、依存性が崩れてエージェントの動作が変わったり正常に動作しなくなったりする可能性がある。

従って、一概にアーキテクチャCが良いとも言い切れないかと思います。また、いきなりアーキテクチャCを思いつくことができるか?疑問です。私の場合は3章で説明したような検討と実際にプログラムを動かして理解が進んだことで頭の中が整理されて3.9のフローを作ることができて、さらにはアーキテクチャCへと進むことができました。アーキテクチャBの実装をしないまでも3章の検討を行った上で、さらに上記課題を検討しないとアーキテクチャCは難しいと思います。機会がありましたら、アーキテクチャCも実装してみて比較評価を行いたいです。

5. まとめ

 LangChainを使うことで非常に柔軟に実装を行えることが分かりました。特に、前回の記事で明らかとなった課題を解決できることが分かりました。しかし、柔軟であることで開発者の責任はより大きく、詳細で深い検討が必要であることも分かりました。

付録: 使ったパッケージのバージョン番号

boto3==1.42.0
chromadb==1.3.5
huggingface-hub==0.36.0
langchain==1.1.0
langchain-aws==1.1.0
langchain-classic==1.0.0
langchain-community==0.4.1
langchain-core==1.1.0
langchain-huggingface==1.1.0
langchain-mcp-adapters==0.1.14
langchain-text-splitters==1.0.0
sentence-transformers==5.1.2

4
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
4
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?