技術blogのリンクを投げたらChatGPTが要約して、いい感じに整形してチャンネル投稿してくれるbotを社内Slackに生やしたら捗った話
こんにちは、株式会社シグマアイのエンジニアの@k_muroです。
今回の記事は最近導入した「技術blogを良い感じに共有してくれるSlack bot」のご紹介を。
はじめに
技術の進化は止まらない。(真面目な話、AI系の進捗がマジですごいて全然追えない)
毎日のように新しい技術、フレームワーク、ライブラリ、ツールが生まれています。そんな中でエンジニアとして働いていると、この情報の波に疲れを感じること、ありませんか? ありますよね?(脅迫)
実際私もその一人で、この小さな疲れが積み重なって大きなストレスとなることに気づきました。
「新しい技術情報、追いつけるかな?」
「あのブログ記事、後で読もうと思ってたのに、どこいったっけ?」
「チーム全員が同じ情報を持ってるか心配だな。」
そんな日常の疑問や不安から逃れるための一歩として、私はあるSlack botを開発しました。このbotは、送られた技術ブログのリンクから内容をAIで要約し、その精度の高い要約を見やすく整形して、特定のチャンネルに投稿してくれます(べんり!!)。記事は色分けされたカテゴリで瞬時に分類。さらに、OGP画像も取得されて、その全てがNotionに保存されるため、後で読み返すことも簡単です。
「はっ、これなら情報を追いつくことが楽になるかも!」と感じたあなた。この記事は、そんなあなたにとっての救世主になるかもしれません。こちらでbotの具体的な機能と実装、実際に社内でどれだけ影響があったのか、そして今後の展開について詳しく紹介していきます。
作ろうとしたきっかけ
他のメンバーから共有された記事を読むのがだるかったからChatGPTに要約してもらいたかった
ChatGPTの料金を会社に支払ってもらいたかったから
技術情報にキャッチアップするため & コミュニケーションを加速するためにChatGPTを活用したいと考えたのがきっかけです。
完成イメージ
使い方は簡単!
↑bot宛てにDMで技術blogのURLを送ると、
10^4300年までに解決しないといけない問題
botが良い感じに要約 & 整形したものを特定のチャンネルに投稿してくれる!!(べんり!!)
記事の内容でざっくりカテゴライズして、そのカテゴリごとに色分けがなされています。(たとえば、デザイン系は黄色みたいな)
こんな感じの色分けです
チャンネル内で、bot宛てに直接URLを投げつけても反応してくれます。
Notion
投稿された記事は、Notionにまとめられています。(なおあんまりNotionは見られてない気もします)
構成
どのような構成かざっくり紹介。
Slack Boltが中心となって、Slack, ChatGPT, NotionのAPIを叩いています。
全体構成図
使っている技術について
語りだすとキリがないのですが、いくつか要点を絞って紹介。
ChatGPT: Function calling
Function callingとは、ChatGPTの呼び出しにおいて、プロンプトに応じた関数を呼び出せるようにしてくれる機能です。いろいろと説明をすっ飛ばした結論を書くとChatGPTから返ってくるお返事を、こちらが想定したフォーマットに従わせられます。
今回はこのFunction callingを使って、「記事の内容の要約とタグを必ずJSON形式で返答すること」を約束させています。
Function callingの説明については以下の記事が詳しいので参考にしてください。
- OpenAIのFunctionCallingを理解する
- [OpenAI] Function callingで遊んでみたら本質が見えてきたのでまとめてみた | DevelopersIO
- ChatGPT の Function calling で確実に JSON 形式で出力できるのかシュッと確認してみた|bbz
Slack: Bolt
Slack Boltは、Slackで動作するBotを簡単に作成するためのフレームワークです。普通にプログラミングするよりも簡単に、特定の動作や機能をSlack内で実行できるようにすることができます。
例えば、誰かが特定のワードをチャットで使ったら自動的に応答する、または毎朝定時に「おはよう」とメッセージを送る、などができます。
今回の実装では、ユーザーからのSlackの投稿を受け付けてからの一連の流れをSlack Boltが担っています。
この処理はSlack Blotの機能の初めの部分にすぎず、ショートカットの機能を実装したり、モーダルを使ってSlackに疑似的なGUIを提供することができます。
Slack Boltの説明については、以下の記事が詳しいので参考にしてください。
作ってみよう!
実装についてお話します!
用意するもの
Slack
-
SLACK_APP_TOKEN
- Slack Boltをソケットモードで実行するために必要なToken
- こちらを参考に取得してください:Slack ソケットモードの最も簡単な始め方 #Slack - Qiita
-
SLACK_BOT_TOKEN
- SlackのBotのToken
- こちらを参考に取得してください:Slack Python SDK でチャンネルにメッセージを投稿しよう #Python - Qiita
ChatGPT
-
OPENAI_API_KEY
- ChatGPT APIを使うためのAPI Key
- ※有料ですが、私は会社の経費で落としています☆彡
- こちらを参考に取得してください:openAIのAPIキーの取得とPython openaiモジュールの動作確認メモ | Oval Design Studio
- ChatGPT APIを使うためのAPI Key
Notion
-
NOTION_APP_TOKEN
- Notion APIを使うためのAPI Token
- こちらを参考に取得してください:Notion API × Python
-
NOTION_DATABASE_URL
- データを追加する先のNotionデータベースがあるページのURL
- こちらを参考に取得してください:Notion API × Python
-
NOTION_DATABASE_ID
- データを追加する先のNotionデータベースのID
- こちらを参考に取得してください:Notion API × Python
実際のコード
実装
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from extractcontent3 import ExtractContent
import requests
import re
from bs4 import BeautifulSoup
import openai
import tiktoken
from tiktoken.core import Encoding
from notion_client import Client
from slack_sdk import WebClient
import traceback
import json
from dotenv import load_dotenv
load_dotenv()
SLACK_APP_TOKEN = os.environ["SLACK_APP_TOKEN"]
SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
ADMIN_SLACK_ID = os.environ["ADMIN_SLACK_ID"] # 管理者のSlackユーザーID
TECH_RANDOM_CH_ID = os.environ["TECH_RANDOM_CH_ID"] # 投稿先のSlackチャンネルのID
TECH_RANDOM_CH_NAME = "#tech-random"
# OGPイメージが取得できなかったときに表示する画像
DEFAULT_OGP_IMAGE = os.environ["DEFAULT_OGP_IMAGE"]
NOTION_APP_TOKEN = os.environ["NOTION_APP_TOKEN"]
NOTION_DATABASE_ID = os.environ["NOTION_DATABASE_ID"]
NOTION_DATABASE_URL = os.environ["NOTION_DATABASE_URL"]
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
openai.api_key = OPENAI_API_KEY
category_json_file = open("category_dict.json", 'r')
CATEGORY_DICT = json.load(category_json_file)
functions_json_file = open("functions_dict.json", 'r')
FUNCTIONS_DICT = json.load(functions_json_file)
app = App(token=SLACK_BOT_TOKEN)
class ArticleSummarizer:
"""
与えられたイベントに基づいて、記事のサマリーを生成し、関連情報を取得・投稿するクラス。
"""
MAX_TOKENS = 20000
TOKEN_LIMIT = 16000
MESSAGE_TRUNCATE_LENGTH = 1000
def __init__(self, event, say, thread_ts=None):
"""
初期化メソッド。イベント情報を基に、関連する属性を設定する。
"""
def get_link(text):
pattern = "https?://[\w/:%#\$&\?\(\)~\.=\+\-]+"
return re.findall(pattern, text)
def get_title_from_url():
r = requests.get(self.url)
soup = BeautifulSoup(r.content, "html.parser")
return soup.find("title").text
self.event = event
self.say = say
self.thread_ts = thread_ts
self.url = get_link(event['text'])[0]
self.user = event['user']
self.title_origin = get_title_from_url()
self.title = ""
self.summary_json, self.ogp_image = self.generate_summary_from_url(self.url)
def handle_error(self, e):
"""
エラーハンドリングを行い、エラーメッセージを通知する。
"""
self.say(
f"Error occurred. Contact <{ADMIN_SLACK_ID}>:\n{traceback.format_exc()}")
print(f"Error occurred: {traceback.format_exc()}")
def generate_summary_from_url(self, url):
"""
URLを引数に取り、そのコンテンツを抽出して要約とタグを生成する。
返り値: 要約とタグのjsonオブジェクト、記事のタイトル、ogp画像。
"""
def extract_content_from_url(url):
"""URLからコンテンツとタイトルを抽出するヘルパー関数"""
res = requests.get(url)
html = res.text
extractor = ExtractContent()
opt = {"threshold": 0}
extractor.set_option(opt)
extractor.analyse(html)
text, _ = extractor.as_text()
soup = BeautifulSoup(html, "html.parser")
title = soup.find("title").text
return text, title, soup
def extract_ogp_image(soup):
"""OGP画像を抽出するヘルパー関数"""
og_image_elems = soup.select('[property="og:image"]')
for og_image_elem in og_image_elems:
return og_image_elem.get("content")
return None
def get_tag_existing_list():
notion = Client(auth=NOTION_APP_TOKEN)
db = notion.databases.query(
**{
'database_id': NOTION_DATABASE_ID # データベースID
}
)
tag_existing_list = []
for i in db["results"]:
for j in i["properties"]["タグ"]['multi_select']:
tag_existing_list.append(j["name"])
tag_existing_list = list(set(tag_existing_list))
return tag_existing_list
text, title, soup = extract_content_from_url(url)
ogp_image = extract_ogp_image(soup)
# 既存のタグを抽出
tag_existing_list = get_tag_existing_list()
# GPT-3.5-turboを呼び出して要約とタグを生成
tag_attention_str = "タグの例は以下です。タグの個数は5個以下でお願いいたします。" + \
''.join("\n- " + i for i in tag_existing_list)
tokens_count = self.MAX_TOKENS
while tokens_count >= self.TOKEN_LIMIT:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "assistant", "content": f"【Title】{title}\n###\n{text}"},
{"role": "user", "content": f"この記事の内容について、技術的な視点で要約とタグ付けをしてください。{tag_attention_str}\nlang:ja"},
]
messages_txt = ''.join(message["content"] for message in messages)
encoding: Encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
tokens = encoding.encode(messages_txt)
tokens_count = len(tokens)
text = text[:-self.MESSAGE_TRUNCATE_LENGTH]
functions = [FUNCTIONS_DICT]
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-16k",
temperature=0.0,
messages=messages,
functions=functions,
function_call={"name": "i_am_json"},
)
message = response["choices"][0]["message"]
return json.loads(message["function_call"]["arguments"]), ogp_image
def generate_and_send_summary(self):
"""
サマリーと関連情報を取得し、レスポンスとして投稿するメインメソッド。
"""
try:
summaries, tag_str, category_name, notion_url = self.prepare_summary()
self.send_summary_to_slack(summaries, tag_str, category_name, notion_url)
except Exception as e:
# どこかでコケたらエラー処理する
self.handle_error(e)
def prepare_summary(self):
"""
サマリーと関連情報を準備する。OGP画像の設定、サマリーの生成、カテゴリーの判定、Notionへの挿入を行う。
"""
def judge_category(text):
"""
与えられたテキストから、最も関連性が高いカテゴリーを決定する。
"""
def calculate_category_score(category_words):
# カテゴリーの単語リストを基に、テキストとの関連性スコアを計算する。
return sum(word_score for word, word_score in category_words.items() if word in text)
categories_scores = {
category_name: calculate_category_score(category_data["word_dict"])
for category_name, category_data in CATEGORY_DICT.items()
}
return max(categories_scores, key=categories_scores.get, default="12.その他")
def make_summary_tag():
# サマリーの作成
summaries = "\n".join(
"• " + (self.summary_json["summary"][f"summary{i+1}"][:125] + "(以下略)" if len(self.summary_json["summary"][f"summary{i+1}"]) >= 125 else self.summary_json["summary"][f"summary{i+1}"])
for i in range(5) if f"summary{i+1}" in self.summary_json["summary"]
)
# タグlistの作成
tag_list = []
for tag_data in self.summary_json["tags"]:
tag = tag_data["tag"] if isinstance(tag_data, dict) else tag_data
tag_list.append({"name": tag})
tag_str = " / ".join([tag["name"] for tag in tag_list])
return summaries, tag_str, tag_list
def insert_notion_database(summaries, tag_list, category_name):
ogp_image = self.ogp_image
if "https://qiita-user-contents.imgix.net" in self.ogp_image:
ogp_image = self.ogp_image.replace("%", "%25")
notion = Client(auth=os.environ['NOTION_APP_TOKEN'])
response = notion.pages.create(
**{
'parent': {'database_id': NOTION_DATABASE_ID},
'properties': {
'名前': {'title': [{'text': {'content': self.title}}]},
'要約': {'rich_text': [{'text': {'content': summaries}}]},
'URL': {'url': self.url},
'タグ': {'multi_select': tag_list},
'カテゴリ': {"select": {"name": category_name}},
},
"children": [
{
"object": 'block',
"type": 'image',
"image": {"type": "external", "external": {"url": ogp_image}}
}
]
}
)
notion_url = response["url"]
return summaries, notion_url
if self.ogp_image is None:
self.ogp_image = DEFAULT_OGP_IMAGE
summaries, tag_str, tag_list = make_summary_tag()
category_name = judge_category(self.title + "\n" + summaries + "\n" + tag_str)
summaries, notion_url = insert_notion_database(summaries, tag_list, category_name)
return summaries, tag_str, category_name, notion_url
def send_summary_to_slack(self, summaries, tag_str, category_name, notion_url):
"""
生成されたサマリーと関連情報を基に、レスポンスメッセージを作成して投稿する。
"""
self.title = f"<{self.url}|*{self.title_origin}*> "
tag_str = f"*[タグ]* {tag_str} "
summaries = f"```{summaries}```"
text_notion = f":notion: <{notion_url}|記事>、<https://www.notion.so/{NOTION_DATABASE_URL}|まとめ>"
if self.thread_ts is None:
# DMで返信
self.say(self.title + "\n" + summaries + "\n" + tag_str + "\n`" + category_name + "`\n" + text_notion)
# チャンネルに投稿
self.send_message_to_channel(
summaries, tag_str, category_name, text_notion, self.user, self.ogp_image)
def send_message_to_channel(self, text_summary, text_tag, text_category, text_notion, user, ogp_image, thread_ts=None):
"""
Slackの特定のチャンネルにメッセージを送信する。
"""
def send_message(client, text_title, attachments, thread_ts=None):
"""Slackへのメッセージの投稿を助けるヘルパー関数"""
params = {
"text": text_title,
"channel": TECH_RANDOM_CH_NAME,
"attachments": attachments
}
if thread_ts:
params["thread_ts"] = thread_ts
response = client.chat_postMessage(**params)
return response
attachments_0 = [
{
"color": CATEGORY_DICT[text_category]["color"],
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": self.title+"\n"+text_tag
},
"accessory": {
"type": "image",
"image_url": ogp_image,
"alt_text": "ogp_image"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": f"{text_notion} by <@{user}> `{text_category}`"
}
]
}
]
}
]
attachments_1 = [
{
"color": CATEGORY_DICT[text_category]["color"],
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": text_summary
}
}
]
}
]
client = WebClient(os.environ["SLACK_BOT_TOKEN"])
if thread_ts is None:
response = send_message(client, None, attachments_0)
send_message(client,self.title_origin, attachments_1, response["ts"])
else:
send_message(client, None,attachments_0, thread_ts)
send_message(client, self.title_origin, attachments_1, thread_ts)
@app.message("") # DMでリンクが送られたときのハンドラ
def reply_message(event, say):
if event['channel_type'] == "im":
asc = ArticleSummarizer(event, say)
asc.generate_and_send_summary()
@app.event("app_mention")
def reply_mention(event, say):
thread_ts = event["ts"]
if event["channel"] == TECH_RANDOM_CH_ID:
asc = ArticleSummarizer(event, say, thread_ts)
asc.generate_and_send_summary()
@app.event("message")
def handle_message_events(body, logger):
logger.info(body)
SocketModeHandler(app, SLACK_APP_TOKEN).start()
その他のコード
SLACK_BOT_TOKEN="xoxb-xxxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
SLACK_APP_TOKEN="xapp-1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
NOTION_APP_TOKEN="secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
NOTION_DATABASE_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
NOTION_DATABASE_URL="https://www.notion.so/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ADMIN_SLACK_ID="@U0XXXXXXXXX"
DEFAULT_OGP_IMAGE="https://xxxxxxxxxxxx.jpg"
TECH_RANDOM_CH_ID="C0XXXXXXXXX"
以下は、記事のカテゴリ分けをするための点数の設定のjsonです。
興味のある分野ごとに単語と点数を設定し、ワードマッチングでスコアを計算し、合計点が最も高かったカテゴリを設定しています。(正直かなりアナログです)
ちなみに、この単語の列挙はChatGPT先生にやってもらいました。 https://chat.openai.com/share/274f788a-a1ea-410a-89f6-36815b61ee58
{
"11.技術と社会": {
"color": "#F3A9A4",
"word_dict": {
"偽情報対策": 100,
"自治体業務": 70,
"社会": 90,
"政策": 80,
"政治": 85,
"デジタルデバイド": 60,
"テクノロジーイーサリエント": 65,
"データプライバシー": 75,
"AI倫理": 80
}
},
"10.情報と知識": {
"color": "#F3D1A4",
"word_dict": {
"メンタルマップ": 90,
"知識定着": 100,
"教育": 95,
"アウトプット": 70,
"情報科学": 85,
"コンテンツカレーション": 75,
"知識グラフ": 80,
"認知心理学": 60,
"ドキュメント": 45
}
},
"09.デザインとUX": {
"color": "#EEF3A4",
"word_dict": {
"フロントエンド": 40,
"UX": 100,
"UI": 50,
"デザイン": 55,
"Figma": 80,
"CSS": 85,
"ユーザーレイアウト": 70,
"コンポーネント": 75,
"ワイヤーフレーム": 65,
"プロトタイピング": 60,
"やフレームワーク": 30
}
},
"08.データ分析とデータサイエンス": {
"color": "#C7F3A4",
"word_dict": {
"データサイエンティスト": 90,
"感情分類": 70,
"データ分析": 100,
"可視化": 75,
"データサイエンス": 95,
"自然言語処理": 85,
"数値解析": 65,
"テキスト分析": 80,
"ビッグデータ": 60,
"クラスタリング": 55,
"統計学": 80,
"特徴量": 70,
"マシンラーニング": 90
}
},
"07.ソフトウェア開発とプロジェクト管理": {
"color": "#A4F3A9",
"word_dict": {
"ソフトウェア": 90,
"プロジェクト": 85,
"技術的負債": 70,
"マネジメント": 80,
"開発": 50,
"レガシーコード": 60,
"コミュニケーション": 75,
"成長": 65,
"評価": 55,
"SQL": 75,
"アジャイル": 70,
"スクラム": 65,
"DevOps": 80,
"テスト駆動開発": 60,
"リファクタリング": 55
}
},
"06.セキュリティ": {
"color": "#A4F3D1",
"word_dict": {
"セキュリティ": 100,
"SSH": 70,
"攻撃": 80,
"URL設計": 60,
"認証": 85,
"リモート接続": 65,
"鍵": 55,
"暗号化": 90,
"ハッキング": 75,
"ツーファクタ認証": 80,
"VPN": 70,
"ファイアウォール": 60
}
},
"05.開発環境": {
"color": "#A4EEF3",
"word_dict": {
"Linux": 100,
"環境": 10,
"Ubuntu": 10,
"Windows": 10,
"Visual Studio Code": 80,
"Docker": 95,
"Dockerfile": 75,
"イメージ": 70,
"コンテナ": 75,
"仮想化": 60,
"Docker Compose": 65,
"GitHub": 85,
"git": 80,
"CI/CD": 90,
"Jenkins": 70,
"環境変数": 65,
"シェルスクリプト": 60,
"Git": 50
}
},
"04.クラウド": {
"color": "#A4C7F3",
"word_dict": {
"AWS": 100,
"GCP": 90,
"Azure": 90,
"クラウド": 95,
"EC2": 80,
"サーバーレス": 70,
"Lambda": 75,
"VPC": 65,
"S3": 80,
"マイクロサービス": 70,
"ロードバランサー": 30
}
},
"03.AIと機械学習": {
"color": "#A9A4F3",
"word_dict": {
"ChatGPT": 85,
"AI": 100,
"人工知能": 95,
"機械学習": 90,
"プロンプト": 70,
"ベイズ": 75,
"ディープラーニング": 80,
"ニューラルネットワーク": 75,
"強化学習": 65,
"教師あり学習": 60,
"教師なし学習": 60,
"OpenAI": 40,
"LLM": 50,
"GPT": 15
}
},
"02.最適化と量子コンピュータ": {
"color": "#D1A4F3",
"word_dict": {
"量子コンピュータ": 100,
"最適": 85,
"QUBO": 70,
"アニーリング": 75,
"量子アルゴリズム": 80,
"量子ビット": 65,
"スーパーポジション": 60,
"量子エンタングルメント": 55
}
},
"01.十八番": {
"color": "#E4A4F3",
"word_dict": {
"API": 15,
"アルゴリズム": 15,
"コーディング": 15,
"Python": 100,
"REST": 80,
"flask": 70,
"プログラミング": 95,
"技術": 90,
"Slack": 65,
"コード": 10
}
},
"12.その他": {
"color": "#E8E8E8",
"word_dict": {
"•": 10
}
}
}
以下は、Function callingをする際にChatGPTに投げる際の指示書であるjsonです。
{
"name": "i_am_json",
"description": "要約の生成と、タグ付けを行います。",
"parameters": {
"type": "object",
"properties": {
"summary": {
"type": "object",
"description": "4つの文章で構成される要約です",
"properties": {
"summary1": {
"type": "string",
"description": "箇条書きで構成された要約文の1文目です。"
},
"summary2": {
"type": "string",
"description": "箇条書きで構成された要約文の2文目です。"
},
"summary3": {
"type": "string",
"description": "箇条書きで構成された要約文の3文目です。"
},
"summary4": {
"type": "string",
"description": "箇条書きで構成された要約文の3文目です。"
}
}
},
"tags": {
"type": "array",
"description": "技術blogに投稿する際に設定されるタグです。個数は5つ以下です",
"maxItems": 5,
"minItems": 1,
"items": {
"type": "object",
"properties": {
"tag": {
"description": "技術blogに投稿する際のタグのひとつです。",
"type": "string"
}
}
}
}
}
}
}
返ってきたjsonの例
{
"role": "assistant",
"content": null,
"function_call": {
"name": "i_am_json",
"arguments": "{\n \"summary\": {\n \"summary1\": \"VS Code拡張機能を使用して、ワークスペース内のすべてのC、ヘッダーファイルに42headerを追加する方法を紹介\",\n \"summary2\": \"VS Codeの拡張機能開発の手順やAPIの活用方法について解説\",\n \"summary3\": \"マルチワークスペース用の便利なAPIやNode.jsのfsモジュールの代替方法について説明\",\n \"summary4\": \"Moment.jsを使用して日時の処理を行い、完成した拡張機能をMarketplaceに公開する手順を紹介\"\n },\n \"tags\": [\n {\"tag\": \"VS Code拡張機能\"},\n {\"tag\": \"開発手順\"},\n {\"tag\": \"API活用\"},\n {\"tag\": \"Node.js\"},\n {\"tag\": \"Moment.js\"},\n {\"tag\": \"Marketplace公開\"}\n ]\n}"
}
}
皆様の声
普段からこのbotを使っていただいている皆様の声を紹介します。
「やりとりが活性化した」、「便利」というご意見が多くもらっていて、実際にこのbotの導入後に社内のコミュニケーションが活発化しているのを肌で感じることが多く、私としては感無量の気持ちです。
保守神はちょおっと恐れ多いかな?w(圧を感じる)
今後の展開として更なるコミュニケーションの加速を考えています。
具体的には、
- 技術記事だけでなく事業に関する記事についても受け付けて、要約の投稿先を自動で判別させたい
- 何らかの形で記事をサジェストする
- X(Twitter)でバズっている技術記事を取得して投稿とか?
- 技術記事の内容を学習して技術コンサルタントになってもらう
などなど。
ぜひ皆様も社内Slackにこのbotを生やしてみてください。
なにかご質問・ご指摘・ごマサカリがあればお気軽にコメントください!!
Discussion