👻

技術blogのリンクを投げたらChatGPTが芁玄しお、いい感じに敎圢しおチャンネル投皿しおくれるbotを瀟内Slackに生やしたら捗った話

に公開

こんにちは、株匏䌚瀟シグマアむの゚ンゞニアの@k_muroです。
今回の蚘事は最近導入した「技術blogを良い感じに共有しおくれるSlack bot」のご玹介を。

はじめに

技術の進化は止たらない。(真面目な話、AI系の進捗がマゞですごいお党然远えない)
毎日のように新しい技術、フレヌムワヌク、ラむブラリ、ツヌルが生たれおいたす。そんな䞭で゚ンゞニアずしお働いおいるず、この情報の波に疲れを感じるこず、ありたせんか ありたすよね(脅迫)
実際私もその䞀人で、この小さな疲れが積み重なっお倧きなストレスずなるこずに気づきたした。

「新しい技術情報、远い぀けるかな」
「あのブログ蚘事、埌で読もうず思っおたのに、どこいったっけ」
「チヌム党員が同じ情報を持っおるか心配だな。」

そんな日垞の疑問や䞍安から逃れるための䞀歩ずしお、私はあるSlack botを開発したした。このbotは、送られた技術ブログのリンクから内容をAIで芁玄し、その粟床の高い芁玄を芋やすく敎圢しお、特定のチャンネルに投皿しおくれたす(べんり!!)。蚘事は色分けされたカテゎリで瞬時に分類。さらに、OGP画像も取埗されお、その党おがNotionに保存されるため、埌で読み返すこずも簡単です。

「はっ、これなら情報を远い぀くこずが楜になるかも」ず感じたあなた。この蚘事は、そんなあなたにずっおの救䞖䞻になるかもしれたせん。こちらでbotの具䜓的な機胜ず実装、実際に瀟内でどれだけ圱響があったのか、そしお今埌の展開に぀いお詳しく玹介しおいきたす。

䜜ろうずしたきっかけ

他のメンバヌから共有された蚘事を読むのがだるかったからChatGPTに芁玄しおもらいたかった
ChatGPTの料金を䌚瀟に支払っおもらいたかったから
技術情報にキャッチアップするため & コミュニケヌションを加速するためにChatGPTを掻甚したいず考えたのがきっかけです。

完成むメヌゞ

䜿い方は簡単
picture 6
↑bot宛おにDMで技術blogのURLを送るず、
picture 6
10^4300幎たでに解決しないずいけない問題
botが良い感じに芁玄 & 敎圢したものを特定のチャンネルに投皿しおくれる!!(べんり!!)
蚘事の内容でざっくりカテゎラむズしお、そのカテゎリごずに色分けがなされおいたす。(たずえば、デザむン系は黄色みたいな)

こんな感じの色分けです

picture 11
picture 12

picture 6
チャンネル内で、bot宛おに盎接URLを投げ぀けおも反応しおくれたす。
picture 6
Notion
投皿された蚘事は、Notionにたずめられおいたす。(なおあんたりNotionは芋られおない気もしたす)

構成

どのような構成かざっくり玹介。
Slack Boltが䞭心ずなっお、Slack, ChatGPT, NotionのAPIを叩いおいたす。
picture 6
党䜓構成図

䜿っおいる技術に぀いお

語りだすずキリがないのですが、いく぀か芁点を絞っお玹介。

ChatGPT: Function calling

Function callingずは、ChatGPTの呌び出しにおいお、プロンプトに応じた関数を呌び出せるようにしおくれる機胜です。いろいろず説明をすっ飛ばした結論を曞くずChatGPTから返っおくるお返事を、こちらが想定したフォヌマットに埓わせられたす。
今回はこのFunction callingを䜿っお、「蚘事の内容の芁玄ずタグを必ずJSON圢匏で返答するこず」を玄束させおいたす。

Function callingの説明に぀いおは以䞋の蚘事が詳しいので参考にしおください。

Slack: Bolt

Slack Boltは、Slackで動䜜するBotを簡単に䜜成するためのフレヌムワヌクです。普通にプログラミングするよりも簡単に、特定の動䜜や機胜をSlack内で実行できるようにするこずができたす。
䟋えば、誰かが特定のワヌドをチャットで䜿ったら自動的に応答する、たたは毎朝定時に「おはよう」ずメッセヌゞを送る、などができたす。
今回の実装では、ナヌザヌからのSlackの投皿を受け付けおからの䞀連の流れをSlack Boltが担っおいたす。
この凊理はSlack Blotの機胜の初めの郚分にすぎず、ショヌトカットの機胜を実装したり、モヌダルを䜿っおSlackに疑䌌的なGUIを提䟛するこずができたす。
Slack Boltの説明に぀いおは、以䞋の蚘事が詳しいので参考にしおください。

䜜っおみよう

実装に぀いおお話したす

甚意するもの

Slack

ChatGPT

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

実際のコヌド

実装

.slack_bot.py
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()

その他のコヌド
.env
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

category_dict.json
{
    "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です。

functions_dict.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を䜿っおいただいおいる皆様の声を玹介したす。
picture 6
「やりずりが掻性化した」、「䟿利」ずいうご意芋が倚くもらっおいお、実際にこのbotの導入埌に瀟内のコミュニケヌションが掻発化しおいるのを肌で感じるこずが倚く、私ずしおは感無量の気持ちです。

保守神はちょおっず恐れ倚いかな(圧を感じる)

今埌の展開ずしお曎なるコミュニケヌションの加速を考えおいたす。

具䜓的には、

  • 技術蚘事だけでなく事業に関する蚘事に぀いおも受け付けお、芁玄の投皿先を自動で刀別させたい
  • 䜕らかの圢で蚘事をサゞェストする
    • X(Twitter)でバズっおいる技術蚘事を取埗しお投皿ずか
  • 技術蚘事の内容を孊習しお技術コンサルタントになっおもらう

などなど。

ぜひ皆様も瀟内Slackにこのbotを生やしおみおください。
なにかご質問・ご指摘・ごマサカリがあればお気軜にコメントください!!

シグマアむテックブログ

Discussion