「人間検索エンジン」を卒業しよう:RAGを活用したテクニカルサポートボットの構築方法

AI tutorial - IT technology blog
AI tutorial - IT technology blog

絶え間ないSlack通知:サポートエンジニアの悪夢

前四半期、私のチームはインフラ全体を新しいKubernetesクラスターに移行しました。それは技術的な大きな節目となるはずでしたが、私のTelegramとSlackの通知は容赦ない波のように押し寄せました。

5分おきに、開発者から「新しいステージングのIngress IPは何?」「CI/CDのシークレットはどこに移った?」といったメッセージが届きます。Confluenceに40ページ以上の詳細なドキュメントがあっても、情報は事実上埋もれていました。多くの人は、使いにくいWikiの検索機能と格闘するよりも、人間に聞いたほうが早いと考えるからです。

このボトルネックはDevOpsやITの現場では一般的です。私たちは週の労働時間の30%(約12〜15時間)を、すでに文書化されている繰り返しの質問への回答に費やしています。重要なセキュリティパッチの対応中にこのような中断が入ることは、単なる煩わしさを超え、大きなコストとなります。私たちは、ドキュメントが自ら答えてくれる仕組みを必要としていました。

なぜキーワード検索は不十分なのか

問題はデータの不足ではなく、情報を取り出す際の手間にあります。従来の検索エンジンはキーワードの一致に頼っています。ジュニアエンジニアが「database connection(データベース接続)」で検索しても、公式ドキュメントで「RDS credentials(RDS認証情報)」という用語が使われていれば、検索結果はゼロになります。その結果、「ねえ、DBの件でちょっといい?」という避けられない問い合わせを招くのです。

GPT-4のような標準的な大規模言語モデル(LLM)は、そのままではこの問題を解決しません。これらは非常に優秀ですが、プライベートネットワークや特定のTerraformスクリプトに関する文脈(コンテキスト)を持っていません。標準的なChatGPTに社内VPNの設定について尋ねれば、一般的な回答を「ハルシネーション(幻覚)」として生成するでしょう。その回答に従えば、本番環境を簡単に壊してしまいかねません。

ファインチューニング vs RAG:現場での結論

私はこれを解決するために2つの道を検討しました。変化の激しいエンジニアリング環境における実際の比較は以下の通りです。

  • ファインチューニング: モデルを独自のドキュメントで再学習させる方法です。洗練されているように聞こえますが、メンテナンスの罠があります。サーバーのIPやポリシーを更新するたびに再学習が必要になり、計算コストに数百ドル、待ち時間に数時間を要します。
  • Retrieval-Augmented Generation (RAG): これはLLMに「持ち込みありの試験」を受けさせるようなものです。「教科書」(ドキュメント)を渡し、回答する前にそこから情報を探すよう指示します。数秒で更新でき、クエリあたりのコストはわずか数円。さらに引用元も示されるため、ソースを検証できます。

RAGの習得は、現代のエンジニアにとって不可欠なスキルです。この手法を選ぶことで、ボットが架空のパスワードや古いSSHコマンドを捏造するのを防ぐことができます。

アーキテクチャ:RAGベースのTelegramボット

最も効果的な解決策は、LLMとベクトルデータベースを組み合わせ、Telegram Bot APIを通じて公開することです。Telegramは技術チームにとって理想的なインターフェースです。モバイル通知の処理が優れており、PagerDutyやGrafanaの通知ですでに利用しているチームも多いでしょう。

1. 基盤のセットアップ

Python、オーケストレーション層としてLangChain、ローカルのベクトルストレージとしてChromaDBを使用します。「脳」の部分には、OpenAIまたはデータプライバシーの要件が厳しい場合はローカルのOllamaインスタンスを使用できます。

pip install langchain langchain-openai chromadb python-telegram-bot pypdf

2. ナレッジベースの構築

PDFやMarkdownファイルを「埋め込み(Embeddings)」に変換する必要があります。これは意味を数学的に表現したものです。これにより、ユーザーが執筆者と異なる用語を使っても、ボットは適切な段落を見つけ出すことができます。

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 社内ドキュメントをロード(例:50ページのオンボーディングPDF)
loader = PyPDFLoader("internal_docs.pdf")
docs = loader.load()

# 文脈を維持するため、オーバーラップを持たせて1000文字のチャンクに分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
chunks = text_splitter.split_documents(docs)

# チャンクをChromaDBにインデックス化
vectorstore = Chroma.from_documents(
    documents=chunks, 
    embedding=OpenAIEmbeddings(),
    persist_directory="./chroma_db"
)

3. リトリーバル(検索)ロジック

次に、ロジックを処理する関数を作成します。ベクトルストアから最も関連性の高い3つのチャンクを検索し、それを GPT-4 に渡して簡潔な回答を合成させます。

from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA

llm = ChatOpenAI(model_name="gpt-4-turbo", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(search_kwargs={"k": 3})
)

def get_answer(question):
    return qa_chain.run(question)

4. Telegramへのデプロイ

これをボットのインターフェースで包むことで、チーム全員が利用できるようになります。開始するには@BotFatherからトークンを取得する必要があります。

from telegram import Update
from telegram.ext import ApplicationBuilder, MessageHandler, filters, ContextTypes

async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_query = update.message.text
    # 視覚的フィードバック:ボットが「考え中」であることをユーザーに知らせる
    await context.bot.send_chat_action(chat_id=update.effective_chat.id, action="typing")
    
    answer = get_answer(user_query)
    await update.message.reply_text(answer)

if __name__ == '__main__':
    app = ApplicationBuilder().token("YOUR_TOKEN").build()
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
    app.run_polling()

運用から得た教訓

ボットをデプロイするのは簡単ですが、信頼性を高めるのは困難です。1ヶ月運用した後、混乱を防ぐために3つの重要な修正を実装しました。

セキュリティの確保

ボットのハンドル名を知っている人なら誰でも社内ネットワーク構成にアクセスできる状態は避けるべきです。私はシンプルなホワイトリストを実装しました。ボットは update.message.from_user.id を承認済み従業員IDのリストと照合します。リストにない場合、ボットは反応しません。

「ハルシネーション防止」プロンプト

LLMは「八方美人」で、「わかりません」と言うのを嫌います。システムプロンプトを次のように修正し、率直に答えるようにしました:「あなたはテクニカルアシスタントです。提供された文脈(コンテキスト)のみを使用してください。答えが見つからない場合は、わからない旨を伝え、#devops-help Slackチャンネルへのリンクを提示してください。決して推測しないでください。」

インデックスの自動更新

ドキュメントは毎日変わります。私たちは、/docs リポジトリのMarkdownファイルがマージされるたびにChromaDBの再インデックスをトリガーするGitHub Actionを設定しました。これにより、ボットが3ヶ月前の古いステージングIPを回答するのを防いでいます。

測定可能な効果

効果はすぐに現れました。Telegramボットを導入してから2週間以内に、主要なサポートチャンネルでの「ちょっとした質問」は60%減少しました。開発者は、人間のエンジニアがコンテキストを切り替えて回答するのを20分待つよりも、ボットによる24時間365日の即答を好みました。

これを構築することは、単なるコーディングの練習ではありません。チームを「セルフサービス文化」へと転換させるための試みです。ドキュメントとの会話が同僚との会話と同じくらい簡単になれば、人々は実際にドキュメントを活用するようになります。複雑なスタックを管理しているなら、これは燃え尽き症候群を避けつつ、自分の専門知識をスケールさせるための最も現実的な方法です。

Share: