初回投稿日:2024年6月17日

先日コラムを書いたDifyですが、何か使いみちがないかを考えてみました。
そんな中でふと思ったのが、退職者が出たときや、育休などで復職の際に引き継ぎをされるときがあると思います。そういったときに問題になるのが「浦島太郎状態」です。
そもそもプロジェクトに参加していない人ならもちろん、参加していた人でもプロジェクトが進んでいるため、居なかった期間の内容を把握するために時間がかかるという点です。

例えば会議資料を見返すというのも手ですが、会議資料も完璧に残っているとは限りません。
会議資料などは先日、日本語に対応したGoogleのNotebookLMなどで対応できると思います。


ただ、一般的に資料だけでやり取りは行われず、最近ではチャットベースで行われる場合が多いです。それをすべてを見返すのは非常に手間です。

そこで、特定のslackのチャンネルと期日を指定することで、それ以降のトーク内容を引っ張り出し回答するチャットを簡易的ではありますが、Difyで作成してみました。

普通に仕組みわかったうえでちょっと使う分にはまねするだけでも使えると思います。
今回はよく使われるslackです。
ただPoC(ポック:概念実証)レベルなので、課題も多いです。(具体的には以下でまとめます)

生成AIが強い点

現状の生成AIは、「大量のデータからのパターン抽出」や「定型的な文章生成」に優れています。
しかし、 「複雑な文脈理解が必要な分析」 「高度な判断を要する意思決定」には十分に対応できません。 このような場合、人間の専門家とAIが協働するアプローチが、長期的に見て効果的かつ信頼性の高い結果得ることができると思います。
例えるなら「賢い助手」のように。あくまで助手として。
そこで今回、大量のチャットログから重要ポイントを抽出し、単純だけど手間で、人間が最終的に内容を確認するという、AIと人間の協働作業にフォーカスを置き試してみることにしました。

実際にDifyに設定してみた

では実際のDifyのフローです。

Dify_slack_flow

回答自体は一番上のルートだけでOKです。
真ん中と下のルートは、今回ダミーのチャンネルを使ったためトークがありません。
そのために適当なトークを作るために下をもうけてみました。(正直なところただ単にイテレーション機能とslack送信機能をつかいたかっただけです)
真ん中がBot User OAuth Tokenを使う形式。一番下がUser OAuth Tokenを使いかつ、送信部分をコードで実装した形式です。

slack API設定画面:https://api.slack.com/apps/

受信部分はAPIを使い、チャンネルの内容を出力するコード(python)を作成しました。

また、slackの回答はUNIX時間で送信、返答してきます。

UNIX時間またはUNIX時刻とはコンピューターシステム上での時刻表現の一種。協定世界時での1970年1月1日午前0時0分0秒から形式的な経過秒数として表される。 真の経過秒数ではなく、その間に挿入された閏秒を引き、削除された閏秒を加えた値である。

引用:ウィキペディア

なので日付をUNIXに変換したりUNIXを日付に戻す必要があったのでその点もコードを実装しました。

さらに、slackからの投稿者の回答も「U*********」のようにIDで帰ってきてしまいます。
それもNotionにリストを作り、照合するようにしました。
ただこれもリスト化できるので、本当は生成AIよりプログラムのほうが適切です。
なぜなら、「回答者不明」とハルシネーションするケースがあるからです。これもナレッジ機能を使いたかっただけではあります。

ベストプラクティスかというと疑問は残りますが、Difyの機能をお試しする分にはいいかなと思いますので、ひとまず詳細を解説します。

Difyの実装内容について

順に説明をしていきます。

開始

まず最初です。「channel_id」と「日付」を入力させるフィールドをつくりました。
channel_idとはslackのここの一番後ろのコードです。ブラウザでslackを表示させたときのここです。

https://app.slack.com/client/A000D0D00NV/C00000S0ABC

いまはダミーの数字をいれていますが、この一番後ろの記号と数字です。
これがどのチャンネルかを指す記号になります。
日付はyyyy/mm/ddで入力してください。例)2020/01/01

次の質問分離機は送信と受信を分けるためです。不要な人は「日付→UNIX」に開始をつなげてください。

日付→UNIX

早速コードを提供します。

def main(time: int) -> dict:

    # 閏年の判定
    def is_leap_year(year):
        return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

    # 日数を計算
    def days_in_month(year, month):
        if month == 2:
            return 29 if is_leap_year(year) else 28
        elif month in [4, 6, 9, 11]:
            return 30
        else:
            return 31

    # 日付をUNIX時間に変換する関数
    def convert_date_to_unix(year, month, day, hour=0, minute=0, second=0):
        # 基準となる1970年1月1日からの経過秒数を計算
        seconds = 0
        for y in range(1970, year):
            seconds += 366 * 86400 if is_leap_year(y) else 365 * 86400

        for m in range(1, month):
            seconds += days_in_month(year, m) * 86400

        seconds += (day - 1) * 86400
        seconds += hour * 3600
        seconds += minute * 60
        seconds += second

        # JSTをUTCに変換するために9時間(32400秒)を引く
        seconds -= 32400

        return seconds

    def main(JST: str) -> dict:
        date_parts = JST.split('/')
        year, month, day = map(int, date_parts[:3])

        if len(date_parts) > 3:
            time_parts = date_parts[3].split(':')
            hour, minute, second = map(int, time_parts)
        else:
            hour, minute, second = 0, 0, 0  # 時分秒がない場合は0にする

        # UNIX時間に変換
        UNIX = convert_date_to_unix(year, month, day, hour, minute, second)
        
        return {
            "unix_time": UNIX
        }

    return main(time)

ライブラリ使わないで変換は普段しませんが手書きするとしたらこんな感じです。
入力変数は以下の画像の通りです。

dify_slack_UNIX

2000/01/01の形式のものをUNIX時間に変換するものです。

slackのチャンネル内容をエクスポート

続いてSlackからのチャット情報を受け取るコードです。

import requests

TOKEN = "ここにAPIをいれる"
SLACK_URL = "https://slack.com/api/conversations.history"

def main(channel_id: int, unix_time: int) -> dict:
    SLACK_CHANNEL_ID = channel_id
    payload = {
        "channel": SLACK_CHANNEL_ID,
        "oldest": unix_time  
        # UNIX時間 1577804400 2020年1月1日0時0分
    }
    headers = {
        'Authorization': f'Bearer {TOKEN}',
    }
    
    try:
        response = requests.get(SLACK_URL, headers=headers, params=payload)
        response.raise_for_status()
        json_data = response.json()
        
        if 'messages' in json_data:
            formatted_msgs = format_messages(json_data['messages'])
            return {"result": formatted_msgs}
        else:
            return {"result": "text_none"}
    
    except (requests.exceptions.RequestException, ValueError):
        return {"result": "text_none"}

def format_messages(messages):
    return " | ".join(
        f"Timestamp: {convert_unix_to_jst(float(message['ts']))}, User: {get_user(message)}, Text: {message['text']}"
        for message in messages
    )

def get_user(message):
    if 'user' in message:
        return message['user']
    elif 'username' in message:
        return message['username']
    else:
        return "Unknown"

# UNIX時間を日付に変換する関数
def convert_unix_to_jst(unix_seconds):
    # JSTに変換するために9時間(32400秒)を足す
    unix_seconds += 32400

    seconds_in_day = 86400
    seconds_in_hour = 3600
    seconds_in_minute = 60

    # 基準となる1970年1月1日からの経過秒数を計算
    seconds = unix_seconds

    # 年の計算
    year = 1970
    while True:
        days_in_year = 366 if is_leap_year(year) else 365
        if seconds < days_in_year * seconds_in_day:
            break
        seconds -= days_in_year * seconds_in_day
        year += 1

    # 月の計算
    month = 1
    while True:
        days_in_current_month = days_in_month(year, month)
        if seconds < days_in_current_month * seconds_in_day:
            break
        seconds -= days_in_current_month * seconds_in_day
        month += 1

    # 日の計算
    day = seconds // seconds_in_day + 1
    seconds %= seconds_in_day

    # 時分秒の計算
    hour = seconds // seconds_in_hour
    seconds %= seconds_in_hour
    minute = seconds // seconds_in_minute
    second = seconds % seconds_in_minute

    return f"{year}/{month:02}/{day:02} {hour:02}:{minute:02}:{second:02}"

# 閏年の判定
def is_leap_year(year):
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

# 日数を計算
def days_in_month(year, month):
    if month == 2:
        return 29 if is_leap_year(year) else 28
    elif month in [4, 6, 9, 11]:
        return 30
    else:
        return 31

difyの環境変数を使えばAPIキーをハードコーディングせずに対応も可能です。

設定内容は以下の通りです。

slacktolk

入力変数や依存関係は画像をご確認ください。
slackのAPIキーについてはちょっとややこしいですが

「xoxp-・・・・・・・・・・・・・・・・・・・・」みたいな形で始まる結構長い文字列です。

これを
“xoxp-・・・・・・・・・・・・・・・・・・・・”
こんな感じで入れてください

slackのトークンにあたえた権限は

– channels:history
– groups:history
– im:history
– mpim:history

です。(slackのAPIは複雑なことができる分設定も複雑です。別記事でまとめるかもしれません)

ID→氏名取得ナレッジ

続いてナレッジです。Notionと連帯させました。Notion側には以下のような形で保存しました。

"ID": "U07602N6XL7"は"氏名": "テスト太郎"

最初オブジェクト形式で書いたんですが逆に精度が微妙で普通に書いたほうがなぜかよかった印象です。

knowledge

生成AIに回答させる

最後要約する生成AIです。

プロンプトは画像の通りで、特に難しいことはしてないです。あとはこの回答を回答ブロックに移すだけです。

slack→Difyの実際の動作状況

実際の動作は動画にしました。
ダミーチャンネルのトーク量が圧倒的に少ないので、もっと会話あった方がよかったかもしれません。

課題点

具体的には
・ユーザー情報の正確性の問題
・都度slackの内容を送っているので生成AIのAPI費用の問題
・添付ファイルの問題
・会議資料との連帯

ほかにも細かくあげれば課題は多くあります。
チャンネルだけじゃなくワークスペースごと。例えば、チャンネルIDもチャンネル名から特定したりなど、細かいUXがらみの点も解消すれば実用レベルまでもちこむことが可能だと思われます。

まとめ

このようにDifyを使ってサービス化しなくても、社内で少し便利するものや、PoC(ポック:概念実証)のための簡易的な作成に利用するのは非常に有用的だと思います。
また生成AIのエンジンを変えるときもDifyであれば簡単です。
実際この作成が数時間で作成できてしまうことに驚きです。

スムーズロゴ
SMOOZ
enterprise-smooz

資料
ダウンロード

マスターデータのメンテナンスに関わる機能をまとめたSaaS「SMOOZ
SMOOZはリレーショナルデータベースの課題を解決するサービスです。
ご興味ございましたら資料をダウンロードください。