NTTドコモR&Dの技術ブログです。

AIスクラムマスターがチームの感情分析をやってみた

アイキャッチ

 

この記事の対象読者

この記事はスクラム開発におけるチーム運営に生成AIを導入してみたい方向けの記事です。

この記事が伝えたいこと

生成AIは生産性向上や自動化だけでなく、人間ならではの仕事(感情理解、チームビルディング)も代替でき、今後AIと人間の新しい関係性を築く試みができました。

はじめに

こんにちは!第一プロダクトデザイン部マーケティングソリューション・マーケティングソリューション第一担当のFusionCrewチームです。メンバーはドコモの小野と協働者の小泉、中西で、担当内横断でAIの新しい活用について検討をしています。私たちのチーム”FusionCrew”では、スクラムマスターの役割をAIに任せる「AIスクラムマスター」の取り組みを構想しています。AIというと生産性向上や自動化がフォーカスされがちですが、今回は少し違ったアプローチに挑戦しました。それは「チームの感情分析」です。

「AIに感情が分かるの?」と思われるかもしれません。しかし、人間には言いづらいことでも、AIが相手なら素直に話せる。AIからのフィードバックなら素直に受け入れられる。そんな可能性を感じて、この取り組みを始めました。

本記事では、感情分析AIクルーちゃん(通称:クルーちゃん)を使った感情分析の実装から運用、運用してみた結果から見えてきた課題までをご紹介します。

 

クルーちゃん

感情分析を取り入れた理由

スクラムチームでは、技術的な進捗だけでなく、メンバーの精神的な健康やチーム内の協調性も非常に重要です。しかし、日々の業務の中で「最近疲れてるな」「ちょっとモヤモヤしてる」といった感情の変化を把握するのは簡単ではありません。

そこで、Slackでの日常会話から感情を自動分析し、チームの状態やエンゲージメントを可視化できないかと考えました。AIスクラムマスターの役割の中でも、比較的着手しやすく、かつ効果が期待できる領域として感情分析を選びました。

感情がプロジェクトに及ぼす影響

  • 疲労の蓄積: 早期に気づけば燃え尽きを防げる
  • 不安の共有: チーム全体で課題を認識できる
  • 喜びの可視化: 小さな成功体験を見逃さない

使用したツール・技術スタック

クルーちゃんは以下の技術で構築しました:

  • 言語: Python
  • インフラ: AWS Lambda, EventBridge
  • AI: Amazon Bedrock (Claude)
  • 認証: AWS STS, IAM
  • データソース: Slack API

最初はローカル環境のAmazon Q Developerでプロンプトの動作確認を行い、その後AWS環境へ移行して自動化しました。

構成図

感情分析の実装プロセス

Pythonコードの実装はAmazon Q Developerを使うことで、わずか数時間で動作するプロトタイプを完成させることができました!

1. データ収集

まずはとあるスクラムチームの雑談チャンネルを想定したものを作成し、有志で適当に会話をしていただきました。そこからSlack APIを使って、会話ログを取得します。

    import os
import datetime
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

# Bot Token
# トークンはサンプルでは直接記載しているが環境変数から取得することを推奨(セキュリティのためハードコードは避ける)
SLACK_BOT_TOKEN = "xoxb-hogehoge" client = WebClient(token=SLACK_BOT_TOKEN) # 対象のチャンネルID(例:generalチャンネル) CHANNEL_ID = "xxxxxxxx" # 日付は後で設定するので、ここではファイル名のテンプレートだけ定義 output_file_template = 'slack_messages_{start}_{end}.txt' try: # 先週の月曜日から日曜日までの範囲を計算 today = datetime.datetime.now() # 今週の月曜日を計算 days_since_monday = today.weekday() this_monday = today - datetime.timedelta(days=days_since_monday) # 先週の月曜日と日曜日を計算 last_monday = this_monday - datetime.timedelta(days=7) last_sunday = last_monday + datetime.timedelta(days=6) # UNIXタイムスタンプに変換(Slack APIで使用) # 月曜日の00:00:00から日曜日の23:59:59まで oldest_ts = last_monday.replace(hour=0, minute=0, second=0, microsecond=0).timestamp() latest_ts = last_sunday.replace(hour=23, minute=59, second=59, microsecond=999999).timestamp() # ファイル名に使用する日付文字列を作成 start_date_str = last_monday.strftime('%Y%m%d') end_date_str = last_sunday.strftime('%Y%m%d') output_file = output_file_template.format(start=start_date_str, end=end_date_str) # チャンネルのメッセージ履歴を取得(先週分のみ)- ページネーション対応 all_messages = [] cursor = None while True: response = client.conversations_history( channel=CHANNEL_ID, oldest=str(oldest_ts), latest=str(latest_ts), limit=1000, cursor=cursor ) all_messages.extend(response["messages"]) if not response.get("has_more"): break cursor = response["response_metadata"]["next_cursor"] with open(output_file, 'w', encoding='utf-8') as f: # 期間情報を追加 f.write(f"# 先週({last_monday.strftime('%Y-%m-%d')}~{last_sunday.strftime('%Y-%m-%d')})のSlackメッセージ\n\n") human_message_count = 0 for message in all_messages: # Botメッセージをスキップ if message.get("subtype") or message.get("bot_id") or not message.get("user"): continue user_id = message.get("user") text = message.get("text") ts = float(message.get("ts")) # 投稿日時を整形(UNIX timestamp → 日時) dt = datetime.datetime.fromtimestamp(ts) timestamp = dt.strftime("%Y-%m-%d %H:%M:%S") # ユーザー名取得 user_info = client.users_info(user=user_id) username = user_info["user"]["profile"]["display_name"] or user_info["user"]["real_name"] f.write(f"【{timestamp}】{username}:{text}\n") human_message_count += 1 print(f"先週({last_monday.strftime('%Y-%m-%d')}~{last_sunday.strftime('%Y-%m-%d')})の人間からのメッセージ {human_message_count}件をUTF-8形式で {output_file} に保存しました。") except SlackApiError as e: print(f"Error fetching messages: {e}")

2. 感情分析プロンプト

Amazon Bedrockを使い、以下のプロンプトで感情を分析します:

    あなたはチームの感情分析を行うAIです。以下の発言を読み取り、
含まれる感情を最大3つまで抽出してください。

感情カテゴリ:
- 喜び(うれしい・達成感・感謝)
- 怒り(不満・苛立ち・怒り)
- 不安(心配・焦り・恐れ)
- 悲しみ(落ち込み・孤独・絶望)
- 疲労(疲れ・燃え尽き・やる気の低下)
- 平常(特に感情は強くない)
- 信頼・協力(チームとの一体感・信頼)

各感情に0〜1.0のスコアを付与してください。

3. 結果の投稿

分析結果をSlackに自動投稿します。

    import os
import sys
import datetime
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

# Bot Token
# トークンはサンプルでは直接記載しているが環境変数から取得することを推奨(セキュリティのためハードコードは避ける)
SLACK_BOT_TOKEN = "xoxb-hogehoge" client = WebClient(token=SLACK_BOT_TOKEN) # 対象のチャンネルID(例:generalチャンネル) CHANNEL_ID = "xxxxxxxx" def send_file_to_slack(file_path): try: # ファイルが存在するか確認 if not os.path.exists(file_path): print(f"エラー: ファイル '{file_path}' が見つかりません。") return False # ファイルを読み込む with open(file_path, 'r', encoding='utf-8') as f: content = f.read() # 現在の日時を取得 now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # メッセージを投稿 message = f"*{os.path.basename(file_path)}の内容* (投稿日時: {now})\n```\n{content}\n```" # 長すぎる場合は分割して送信 if len(message) > 4000: chunks = [message[i:i+4000] for i in range(0, len(message), 4000)] for i, chunk in enumerate(chunks): response = client.chat_postMessage( channel=CHANNEL_ID, text=f"({i+1}/{len(chunks)}) {chunk}" ) else: response = client.chat_postMessage( channel=CHANNEL_ID, text=message ) print(f"ファイル '{file_path}' の内容をSlackに投稿しました。") return True except SlackApiError as e: print(f"Slackへの投稿エラー: {e}") return False except Exception as e: print(f"エラーが発生しました: {e}") return False if __name__ == "__main__": # コマンドライン引数からファイルパスを取得 if len(sys.argv) < 2: print("使用方法: python send-test.py <ファイルパス>") print("例: python send-test.py slack_messages_20230605_20230611.txt") sys.exit(1) file_path = sys.argv[1] send_file_to_slack(file_path)

実際にやってみた結果

クルーちゃんを運用してみて、以下のような結果が得られました:

分析例:

    # Slackメッセージ感情分析結果 (2025-06-30~2025-07-06)

【2025-07-04 13:24:55】メンバーA
元のコメント: 14時までに出社すべく、駅まで車で移動して時間短縮。乗っていた電車が、ホームドア故障で遅延。会議に間に合うかの瀬戸際、、←イマココ
感情: 不安(0.8), 疲労(0.4)
総合評価: negative
コメント: 交通トラブルによる時間的プレッシャーを感じています。事前の余裕時間確保や代替手段の検討をお勧めします。

【2025-07-03 17:58:01】メンバーB
元のコメント: ありがとうございました!
感情: 喜び(0.6), 信頼・協力(0.5)
総合評価: positive
コメント: 感謝の気持ちが表れており、良好な関係性が築けています。

【2025-07-03 17:49:50】メンバーC
元のコメント: すでにクルーちゃんが可愛すぎる
感情: 喜び(0.9)
総合評価: positive
コメント: 非常にポジティブな感情で、チームマスコットに愛着を感じています。チームの雰囲気作りに貢献しています。

【2025-07-03 17:42:44】メンバーD
元のコメント: XXXさんは無事かなー?クルーちゃん教えてー。
感情: 不安(0.5), 信頼・協力(0.7)
総合評価: neutral
コメント: 同僚への気遣いが見られます。チームメンバーを思いやる良い姿勢です。

【2025-07-03 17:41:37】メンバーE
元のコメント: ひさびさのヒールで足ががたがた
感情: 疲労(0.6), 平常(0.4)
総合評価: neutral
コメント: 軽い身体的な不調を表現していますが、深刻ではありません。適度な休憩を心がけてください。

【2025-07-03 17:41:23】メンバーD
元のコメント: YYYさん業務説明が終わったので打ち上げに行きたいなー。
感情: 喜び(0.7), 信頼・協力(0.6)
総合評価: positive
コメント: 業務完了の達成感とチームとの交流を求める前向きな姿勢が見られます。

【2025-07-03 17:41:07】メンバーF
元のコメント: エアコンなしでいこうと思いましたが流石に部屋が30度になったので、今起動しました。。。
感情: 疲労(0.5), 平常(0.5)
総合評価: neutral
コメント: 暑さによる軽い不快感がありますが、適切に対処しています。体調管理を大切にしてください。

【2025-07-03 17:40:56】メンバーG
元のコメント: 地震が多くて怖いですね
感情: 不安(0.8)
総合評価: negative
コメント: 自然災害への不安を感じています。防災準備や情報収集で不安軽減を図ることをお勧めします。

【2025-07-03 17:40:37】メンバーH
元のコメント: 明日休みなので、今週はもう終わり~
感情: 喜び(0.8)
総合評価: positive
コメント: 週末への期待感が表れており、良いワークライフバランスが取れているようです。

【2025-07-03 17:40:29】メンバーI
元のコメント: 寝室のエアコンの温度設定が安定してなくて寒い→暑いの繰り返しで体力が回復しない
感情: 疲労(0.8), 不満(0.6)
総合評価: negative
コメント: 睡眠環境の問題で疲労が蓄積しています。エアコンの修理や設定見直しで睡眠の質改善を図ってください。

【2025-07-03 17:40:28】メンバーJ
元のコメント: 〇〇〇システムが△△すぎるんですけどー
感情: 怒り(0.9), 不満(0.8)
総合評価: negative
コメント: システムに対する強い不満があります。改善提案や代替手段の検討をお勧めします。

【2025-07-03 17:40:03】メンバーB
元のコメント: 緊張しています笑
感情: 不安(0.6), 平常(0.4)
総合評価: neutral
コメント: 適度な緊張感がありますが、「笑」で和らげようとしています。リラックス方法を見つけることをお勧めします。

【2025-07-03 17:40:01】メンバーK
元のコメント: 無事にお披露目会を迎えられて一安心。みんなのリアクションが楽しみ!
感情: 喜び(0.8), 信頼・協力(0.5)
総合評価: positive
コメント: プロジェクト完了への安堵感と期待感が表れています。チームへの信頼も感じられます。

【2025-07-03 17:39:52】メンバーL
元のコメント: 今週は一週間が長く感じますなあ
感情: 疲労(0.7), 平常(0.3)
総合評価: neutral
コメント: 週の疲れが蓄積している状況です。適度な休息とリフレッシュを心がけてください。

【2025-07-03 12:32:53】メンバーB
元のコメント: 梅雨はなかったんじゃないか?って思うほど晴れている気がする。気持ち的には嬉しい。
感情: 喜び(0.7)
総合評価: positive
コメント: 天候に対するポジティブな反応で、気分が良い状態です。

【2025-07-02 12:31:01】メンバーB
元のコメント: ↑マークダウン??の関係でちょっとレイアウト崩れちゃってますm(__)m
感情: 不満(0.4), 平常(0.6)
総合評価: neutral
コメント: 軽い技術的な問題への困惑がありますが、丁寧に対応しています。

【2025-07-01 19:44:33】メンバーK
元のコメント: 木曜日の感情分析AIのお披露目会の準備を細々と進めている。みんなの反応が今から楽しみ。
感情: 喜び(0.6), 信頼・協力(0.4)
総合評価: positive
コメント: プロジェクトへの期待感とチームへの信頼が表れています。準備段階での前向きな姿勢が良好です。

【2025-06-30 17:22:01】メンバーB
元のコメント: 最近お叱りが多く謝ってばかりだけど、その分期待されていると信じて頑張ろうと思う:smiling_face_with_tear:
感情: 悲しみ(0.6), 喜び(0.4), 信頼・協力(0.3)
総合評価: neutral
コメント: 困難な状況でも前向きに捉えようとする姿勢が見られます。サポートと励ましが必要な状況です。

【2025-06-30 17:17:58】メンバーA
元のコメント: 気づけば、6/30。明日から、新しい職場に着任。どんな仕事が待っているか
感情: 不安(0.6), 喜び(0.4)
総合評価: neutral
コメント: 新環境への期待と不安が混在しています。新しいスタートへの自然な反応です。

【2025-06-30 15:59:17】メンバーK
元のコメント: □□のVerUPは想定外事象が発生しやすく予測しにくいので(特にオンプレ)、期限があるような場合はバッファを多めにとっておき早めに着手するのが吉。スコープ調整できない場合は対応できなくてつらい。
感情: 不安(0.5), 疲労(0.4), 信頼・協力(0.6)
総合評価: neutral
コメント: 技術的な課題への懸念がありますが、経験に基づく建設的なアドバイスを提供しています。

## 全体的な傾向
- ポジティブ: 7件 (35%)
- ニュートラル: 9件 (45%)
- ネガティブ: 4件 (20%)

チーム全体としては比較的安定していますが、技術的な課題や環境的な問題による軽度のストレスが見られます。プロジェクトへの期待感とチームワークは良好です。

気づいたこと

驚いたのが感情スコアに関して総合評価含め妥当だったことです。人から見ても「ああ、そうだよね」と納得できる結果が出ていました。また、「笑」などの独特な文章表現もしっかりと認識しており、意図しない評価になっていなかったことにも気づきました。一方フィードバックコメントについてもしっかりと回答しているように見えますが、ありきたりな文章になってしまい、少し薄味な結果になりました。そこはプロンプトの改善や、過去の会話の蓄積、プロジェクト状況の把握をした上でより高品質なフィードバックを返せるように工夫する必要があります。

課題と改善点

課題: そもそも会話が集まらなかった

当初、分析対象のチャンネルでの発言が少なく、十分なデータが集まりませんでした。そもそも雑談チャンネルがないチームについてハードルが高いと考えました。

対策:

  • AIとSlack botを用いた会話の促進
  • 気軽に投稿できる雰囲気づくり
  • Slackだけじゃなく、レトロスペクティブの内容、Teamsでの会議音声等で分析

会話を促す仕組みを試してみた

Slackチャンネルへの投稿を増やすためにいろいろ考えた結果、クルーちゃんが毎日決まった時間に、昨日のSlack投稿から話題をピックアップして一言コメントする仕組みを導入しました。

会話がなかったら会話がなかったことに嘆きます。

    prompt = f"""あなたは感情分析AI「クルーちゃん」です。以下の昨日のチームの会話内容を参考にして、今日のチームの士気を高めるメッセージを日本語で生成してください。

{context_info}

要件:
- 昨日の会話の流れを踏まえた継続性のあるメッセージ
- 前向きで励ましの言葉
- 今日のチームの頑張りを応援する内容
- 300文字以内
- 語尾に「クル」を付けてカジュアルさを演出

例:「昨日の議論を踏まえて、今日も素晴らしい成果を出していきましょう!チーム一丸となって頑張ります!✨」"""

さらに今回からAmazon Bedrockに移行し、LambdaとEventBridgeで自動実行できるようにしました。環境の都合上、Bedrockを実行するために毎回一時認証を取得しないといけないのですが、自動化するにあたり毎回一時認証を自動取得し認証情報を保持したうえで実行する仕組みにしました。地味に自動化するのに難儀でしたが力技で乗り切りました。

これでみんな積極的に会話するだろう…!

会話を促す仕組みを試してみた結果

Slackの会話がなくても前向きなクルーちゃん

散々な結果でした。

やはりSlackで雑談をさせるのを定着させるのは難しいです。クルーちゃんの会話バリエーションも増やす等の工夫も必要かと思いましたが、Slackに拘らずもっと気軽に効率的に感情分析ができるソースを手に入れられないかが課題と感じました。

まとめと今後の展望

AIスクラムマスターによる感情分析は、チームの状態を定量的に把握する新しいアプローチとして有効と感じました。人間ならではの仕事をAIが代替することで、スクラムマスターはより戦略的な活動に集中できます。しかし、感情分析をするためにチームの負荷を増やすことや無理矢理会話を強いることはかえって逆効果になります。普段のチームの営みの中で如何に簡単にかつ楽しく感情分析ができるか今後の課題になりそうです。生成AIと共存していくチームの無限の可能性を感じた取り組みでした。

今後の展望

  • ConfluenceやJiraとの連携: スプリント振り返りデータとの相関分析
  • Slack絵文字・スタンプ対応: より豊かな感情表現の分析
  • 音声感情分析: オンライン会議での感情分析
  • 予測機能: 過去データから燃え尽きリスクを予測

この記事を最後まで読んでいただいた方にはどう感じたでしょうか?生成AIを取り巻く環境は日々進歩しています。近い将来、生成AIを使っていることを意識せずに、「気が付いたら使っている環境になっている」ことが実現されるかもしれません。我々はAIと人間が協力し合う「FusionCrew」として、そんな未来をちょっとだけ先行体験できるように、これからもチームのエンゲージメント向上に取り組んでいきます。

参考資料