演習:カスタマーサポートエージェントを構築しよう
「設計は整った。LangGraphでカスタマーサポートエージェントを組み上げよう。」
田中VPoEが開発環境を開く。
「問い合わせの自動分類、FAQ検索、回答生成、エスカレーション判断まで一気通貫で実装してくれ。」
ミッション概要
LangGraphを使って、NetShop社カスタマーサポート向けのAIエージェントを構築する。
前提条件
- Python 3.10+
- LangGraph, LangChain, OpenAI API
- 分類モデル(BERT Fine-tuning済み)
Mission 1: ツール実装(40分)
以下のツールを実装せよ。
classify_inquiry: BERT分類 + LLMフォールバックsearch_faq: セマンティック検索analyze_sentiment: 感情分析generate_response: RAGベース回答生成
解答例
from langchain_core.tools import tool
@tool
def classify_inquiry(text: str) -> dict:
"""問い合わせを分類"""
# BERT分類
result = classifier.classify(text)
if result['confidence'] < 0.7:
# LLMフォールバック
llm_result = llm_classify_fallback(text)
result['category'] = llm_result['category']
result['fallback'] = True
return result
@tool
def search_faq(query: str, category: str = "") -> list:
"""FAQ検索"""
return faq_engine.search(query, category, top_k=3)
@tool
def analyze_sentiment(text: str) -> dict:
"""感情分析"""
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = f"問い合わせの感情を分析: {text}"
# 構造化出力で sentiment, urgency, frustration_level を返す
return {"sentiment": "negative", "urgency": "medium", "frustration_level": 2}
@tool
def generate_response(inquiry: str, category: str, faq_context: str) -> str:
"""回答生成"""
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
prompt = f"カテゴリ: {category}\nFAQ: {faq_context}\n問い合わせ: {inquiry}\n回答:"
return llm.invoke(prompt).content
Mission 2: LangGraphワークフロー構築(40分)
以下を実装せよ。
- SupportAgentState の定義
- 各ノード(分類、感情分析、エスカレーション判断、FAQ検索、回答生成)
- conditional_edges によるエスカレーション分岐
- グラフのコンパイルと実行
解答例
from langgraph.graph import StateGraph, END
def classify_node(state):
result = classify_inquiry.invoke({"text": state['inquiry_text']})
return {'category': result['category'], 'confidence': result['confidence']}
def sentiment_node(state):
result = analyze_sentiment.invoke({"text": state['inquiry_text']})
return {'sentiment': result}
def escalation_node(state):
needs = (
state['confidence'] < 0.5
or state['sentiment'].get('frustration_level', 0) >= 4
)
return {
'needs_escalation': needs,
'escalation_reason': '確信度低/感情ネガティブ' if needs else None,
}
def faq_node(state):
results = search_faq.invoke({"query": state['inquiry_text'], "category": state['category']})
return {'faq_results': results}
def response_node(state):
faq_text = "\n".join([f"Q: {f['question']}\nA: {f['answer']}" for f in (state['faq_results'] or [])])
response = generate_response.invoke({
"inquiry": state['inquiry_text'],
"category": state['category'],
"faq_context": faq_text,
})
return {'draft_response': response}
def route_escalation(state):
return "escalate" if state['needs_escalation'] else "faq_search"
graph = StateGraph(SupportAgentState)
graph.add_node("classify", classify_node)
graph.add_node("sentiment", sentiment_node)
graph.add_node("escalation_check", escalation_node)
graph.add_node("faq_search", faq_node)
graph.add_node("generate_response", response_node)
graph.add_node("escalate", lambda s: {'messages': [AIMessage(content="エスカレーション通知を送信しました")]})
graph.set_entry_point("classify")
graph.add_edge("classify", "sentiment")
graph.add_edge("sentiment", "escalation_check")
graph.add_conditional_edges("escalation_check", route_escalation)
graph.add_edge("faq_search", "generate_response")
graph.add_edge("generate_response", END)
graph.add_edge("escalate", END)
app = graph.compile()
Mission 3: テストとエッジケース対応(40分)
以下を実施せよ。
- 5つ以上の問い合わせシナリオでテスト
- エスカレーション発動ケースの確認
- 品質レポートの作成
解答例
=== テストシナリオ ===
1. 通常問合せ: 「注文した商品はいつ届きますか」→ 配送カテゴリ → FAQ回答
2. 返品要望: 「商品が壊れていたので返品したい」→ 返品・交換 → 手順回答
3. 怒りの問合せ: 「3回目の問い合わせだ!いい加減にしろ!」→ エスカレーション
4. 法的言及: 「消費者庁に相談する」→ 法務エスカレーション
5. 曖昧な問合せ: 「なんかよくわからないんですけど」→ 確信度低 → エスカレーション
=== 品質レポート ===
| シナリオ | 分類正答 | エスカ判断 | 回答品質 |
|---------|---------|-----------|---------|
| 通常 | OK | 不要(正しい) | 適切 |
| 返品 | OK | 不要(正しい) | 適切 |
| 怒り | OK | 発動(正しい) | - |
| 法的 | OK | 発動(正しい) | - |
| 曖昧 | 低確信度 | 発動(正しい) | - |
達成度チェック
- 4つのツールを実装した
- LangGraphワークフローをコンパイル・実行できた
- エスカレーション分岐が正しく動作した
- 5つ以上のシナリオでテストした
- 品質レポートを作成した
推定所要時間: 120分