現場PMでも“サクっと”作れる!生成AI×バイブコーディングで業務改善ツールを自作する方法
SREホールディングス株式会社でプロジェクトマネージャ(PM)をしている鍋谷です。
「ちょっとした業務改善、誰かに頼むほどでもないけど、毎日ちょっと面倒」
そんな作業、あなたのチームにもありませんか?
本記事では、生成AIとバイブコーディングを活用して、現場のPMや非エンジニアでも“サクっと”作れる業務改善ツールの実例を紹介します。
対象読者
・現場のプロジェクトマネージャ(PM)
・非エンジニアで業務改善に関心がある方
・生成AIやAPIを使った業務効率化に興味がある方
・「自分でもツールを作ってみたい」と思っている方
・SlackやNotionなどのSaaSを日常的に使っている方
本記事の概要
- 生成AIとAPIの組み合わせで、外部委託するほどでもない小規模案件やROIが読みにくい試行のIT化コストが激減。
- 結果として、現場のプロジェクトマネージャ(PM)や非エンジニアが、自分の課題を自分でササっと道具化できる=ITの民主化(=専門知識がなくても業務改善ツールを自作できる環境)が進む。
- 具体例として、Slackの1日分の投稿を収集し、要約/課題の抽出/ToDo/アラートなどを朝会・夕会用に集約するミニツールを紹介。
IT化のハードルが下がった理由
1) 仕様を完全に固めなくても「方向性」を言語化すれば動く
従来は要件定義→設計→実装→レビュー…という直線工程が必須でした。生成AI時代は、こんなアウトプットが欲しいを言語で書くと、雛形(プロンプト/コード/データ整形)が一気に出てきます。そこから小さく回して学習すればよく、先に投資を張らなくても検証できます。
2) 「小さなIT化」が外注コストを超えやすくなった
かつては「人に頼むほどでもない細々とした作業」は非効率のまま手作業で実施したり、あきらめて放置されがちでした。いまはPMがバイブコーディングで1〜2時間あれば作れる簡易スクリプトでも、例えば現場のボトルネックを毎日見える化でき、十分な投資対効果が出せます。

3) 既存SaaSとAPIが“部品化”
SlackやGoogle Workspace、Notion、Jira/Backlog、Microsoft 365などをつなぐだけで実務レベルの自動化になります。バイブコーディングはこの“繋ぐ力”を民主化しました。
バイブコーディングで”ササっと”作る方法
バイブコーディングとは
バイブコーディング(Vibe Coding)とは、仕様を完全に詰める前に「こういう体験・結果が欲しい」という雰囲気(vibe)を自然言語で伝え、生成AIにまず動く叩き台を作らせ、手元で高速に試行錯誤する開発スタイルです。
バイブコーディングの手順
-
バイブコーディングの基本を理解する
コードを自ら書く必要がほぼない。ChatGPTやGeminiなどのAIツールを利用。成功のカギは「明確な要件定義」と「対話力」 -
必要な準備
AIツールの選定: ChatGPTやGeminiなど。できれば有料のほうがよい。
開発環境: ご自身のパソコンでOK。メモ帳やテキストエディタで作成可能。 -
実行手順
ステップ1:作りたいものを明確化
「誰のために」「何を解決するのか」を文章で整理。
ステップ2:AIに役割を与える
最初に「あなたはプロのITエンジニアです。この仕様でアプリを作ってください」と伝える。
ステップ3:要件を自然言語で指示
例「UIがシンプルなToDoアプリを作って。機能は追加・削除・完了チェックのみ」
ステップ4:コード生成と実行
AIが生成したコードをコピーし、エディタでプログラムファイルを作成する。プログラムを動かす方法は「初心者でもわかるように動作させるための手順を教えて」とAIに聞けば教えてくれる。動作させて不具合があればAIに「エラー内容」を伝えて修正依頼。
ステップ5:反復改善
「このデザインをもっとモダンに」「ダークモードを追加」など、会話で改善。 -
作成上のポイント
・小さく始める:最初は簡単なつくりに
・こだわる要件は丁寧に:AIは指示があいまいだと意図しないコードを生成
・AIは意外と間違える:バグや制約があるので根気よく試行錯誤する
実例:Slackの投稿をNotionに集約し、要約・課題・ToDoを抽出する
ゴール:
指定チャンネルの1日分の投稿を収集し、要約/課題/ToDo/アラートを整理してNotionに保存。
NotionAIでレポート形式に要約させ、朝会・夕会に活用します。
プロンプト例
あなたはプロのITエンジニアです。Slackで複数のチャンネルから最近のメッセージをまとめて取得し、それをNotionのページに整理して保存する仕組みを作ってください。
- Slackの複数のチャンネルのメッセージを1日に1回収集する
- Slackのメッセージとスレッドの返信も含めたい
- ユーザー名はIDではなく表示名でわかるようにしたい
- まとめたメッセージをNotionの1ページに集約したい
- 集約されたメッセージはNotionAIにレポート形式で要約させたい。
- できれば前回取得した時刻以降の新しいメッセージだけを対象にしたい
- 設定情報(チャンネル一覧やユーザ名)は簡単にメンテナンスできるようにしたい
- 実装は自動化しやすい方法でお願いします
作成したコード
生成AIで作成したコードをテストして、エラーをAIに報告、修正内容を反映してまたテストというのを繰り返しました。最終的に完成したコードが以下となりました。
このコードはPythonで書かれています
import os
import json
import time
import datetime
import requests
CONFIG_FILE = "slack_channel.json"
TIMESTAMP_FILE = "last_fetch_timestamp.txt"
TEMP_JSON_FILE = "latest_simplified_slack_messages.json"
JST = datetime.timezone(datetime.timedelta(hours=9))
MAX_TEXT_LENGTH = 2000
# 設定読み込み
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
config = json.load(f)
slack_token = config["slack_token"]
notion_token = config["notion_token"]
database_id = config["database_id"]
channel_map = config["channel_map"]
channel_ids = list(channel_map.keys())
headers = {"Authorization": f"Bearer {slack_token}"}
notion_headers = {
"Authorization": f"Bearer {notion_token}",
"Content-Type": "application/json",
"Notion-Version": "2022-06-28"
}
def get_user_map():
response = requests.get("https://slack.com/api/users.list", headers=headers)
user_map = {}
if response.status_code == 200 and response.json().get("ok"):
for user in response.json()["members"]:
user_id = user["id"]
display_name = user["profile"].get("display_name") or user["profile"].get("real_name") or user_id
user_map[user_id] = display_name
return user_map
def replace_user_mentions(text, user_map):
if not isinstance(text, str):
return text
for user_id, name in user_map.items():
text = text.replace(f"<@{user_id}>", f"@{name}")
return text
def fetch_thread_replies(channel_id, thread_ts):
replies = []
params = {
"channel": channel_id,
"ts": thread_ts,
"limit": 100
}
response = requests.get("https://slack.com/api/conversations.replies", headers=headers, params=params)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 1))
print(f"⏳ スレッド取得でレートリミット。{retry_after}秒待機...")
time.sleep(retry_after)
return fetch_thread_replies(channel_id, thread_ts)
data = response.json()
if data.get("ok"):
replies = data.get("messages", [])[1:] # 最初の1件は親メッセージなので除外
else:
print(f"❌ スレッド取得エラー: {data.get('error')}")
return replies
def fetch_channel_messages(channel_id, start_ts, end_ts):
messages = []
cursor = None
total_fetched = 0
while True:
params = {
"channel": channel_id,
"oldest": str(start_ts),
"latest": str(end_ts),
"limit": 200,
}
if cursor:
params["cursor"] = cursor
response = requests.get("https://slack.com/api/conversations.history", headers=headers, params=params)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 1))
print(f"⏳ レートリミットに達しました。{retry_after}秒待機します...")
time.sleep(retry_after)
continue
data = response.json()
if not data.get("ok"):
print(f"❌ APIエラー: channel={channel_id}, error={data.get('error')}")
break
batch = data.get("messages", [])
if not batch:
print(f"ℹ️ API応答は成功したがメッセージが空です: channel={channel_id}")
else:
print(f"✅ メッセージ取得成功: channel={channel_id}, 件数={len(batch)}")
for msg in batch:
messages.append(msg)
if "thread_ts" in msg and msg["thread_ts"] == msg["ts"]:
replies = fetch_thread_replies(channel_id, msg["ts"])
messages.extend(replies)
total_fetched += len(batch)
cursor = data.get("response_metadata", {}).get("next_cursor")
if not cursor:
break
print(f"📥 チャンネル {channel_id}:合計 {len(messages)} 件取得(返信含む)")
return messages
def split_text_into_blocks(content):
blocks = []
while len(content) > MAX_TEXT_LENGTH:
blocks.append(content[:MAX_TEXT_LENGTH])
content = content[MAX_TEXT_LENGTH:]
blocks.append(content)
return blocks
def post_to_notion_append(simplified_data, start_ts, end_ts):
MAX_TEXT_LENGTH = 2000
def split_text_into_blocks(content):
blocks = []
while len(content) > MAX_TEXT_LENGTH:
blocks.append(content[:MAX_TEXT_LENGTH])
content = content[MAX_TEXT_LENGTH:]
blocks.append(content)
return blocks
start_dt = datetime.datetime.fromtimestamp(start_ts, JST)
end_dt = datetime.datetime.fromtimestamp(end_ts, JST)
start_str = start_dt.isoformat()
end_str = end_dt.isoformat()
title = f"Slackメッセージまとめ({start_str}〜{end_str})"
all_blocks = []
for channel_id, messages in simplified_data.items():
channel_name = channel_map.get(channel_id, channel_id)
all_blocks.append({
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{"type": "text", "text": {"content": f"Channel: {channel_name}"}}]
}
})
for msg in messages:
content = f"[{msg['timestamp']}] {msg['user']}: {msg['text']}"
if msg["reply_to"]:
content += f"\n↳ Reply to: {msg['reply_to']}"
for chunk in split_text_into_blocks(content):
all_blocks.append({
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": chunk}}]
}
})
first_blocks = all_blocks[:100]
payload = {
"parent": {"database_id": database_id},
"properties": {
"Name": {
"title": [{"type": "text", "text": {"content": title}}]
},
"取得開始時刻": {
"date": {"start": start_str}
},
"取得終了時刻": {
"date": {"start": end_str}
}
},
"children": first_blocks
}
response = requests.post("https://api.notion.com/v1/pages", headers=notion_headers, json=payload)
if response.status_code != 200:
print("❌ Notionページ作成エラー:", response.text)
return
page_id = response.json().get("id")
print("✅ Notionページが作成されました:", page_id)
for i in range(100, len(all_blocks), 100):
chunk = all_blocks[i:i + 100]
append_url = f"https://api.notion.com/v1/blocks/{page_id}/children"
append_payload = {"children": chunk}
append_response = requests.patch(append_url, headers=notion_headers, json=append_payload)
if append_response.status_code == 200:
print(f"➕ {len(chunk)} ブロックを追記しました")
else:
print("❌ 追記エラー:", append_response.text)
def main():
if os.path.exists(TIMESTAMP_FILE):
with open(TIMESTAMP_FILE, "r") as f:
start_ts = float(f.read().strip())
else:
start_ts = time.time() - 86400
end_ts = time.time()
start_ts = int(start_ts)
end_ts = int(end_ts)
print("🔍 取得範囲(JST):")
print("開始:", datetime.datetime.fromtimestamp(start_ts, JST))
print("終了:", datetime.datetime.fromtimestamp(end_ts, JST))
user_map = get_user_map()
all_messages = {}
parent_lookup = {}
for channel_id in channel_ids:
messages = fetch_channel_messages(channel_id, start_ts, end_ts)
time.sleep(1)
if not messages:
print(f"⚠️ チャンネル {channel_id} にメッセージがありません")
all_messages[channel_id] = messages
for msg in messages:
ts = msg.get("ts")
text = replace_user_mentions(msg.get("text", ""), user_map)
if ts and text:
parent_lookup[ts] = text
simplified_data = {}
for channel_id, messages in all_messages.items():
simplified_data[channel_id] = []
for msg in messages:
ts = msg.get("ts", "")
user = msg.get("user", msg.get("bot_id", ""))
text = replace_user_mentions(msg.get("text", "").replace("\n", " ").strip(), user_map)
thread_ts = msg.get("thread_ts", "")
is_reply = thread_ts and thread_ts != ts
reply_to_text = parent_lookup.get(thread_ts, "") if is_reply else ""
try:
readable_time = datetime.datetime.fromtimestamp(float(ts), JST).strftime("%Y-%m-%d %H:%M:%S")
except:
readable_time = ts
simplified_data[channel_id].append({
"timestamp": readable_time,
"user": user_map.get(user, user),
"text": text if text else "(No text)",
"reply_to": reply_to_text
})
if os.path.exists(TEMP_JSON_FILE):
os.remove(TEMP_JSON_FILE)
with open(TEMP_JSON_FILE, "w", encoding="utf-8") as f:
json.dump(simplified_data, f, ensure_ascii=False, indent=2)
post_to_notion_append(simplified_data, start_ts, end_ts)
with open(TIMESTAMP_FILE, "w") as f:
f.write(str(end_ts))
if __name__ == "__main__":
main()
できあがったコードの処理概要
-
Slack APIで指定チャンネル群の当日投稿を取得(
conversations.history)。 - 投稿を時系列で整形(スレッドは親にネスト)。
- Notion APIで日次のまとめページを作成。
NotionAIをコードから直接操作しようとしたが、現時点ではNotionAIのAPIが公開されていないため、「要約/課題/ToDo」の抽出はNotion AIに手動で依頼することとしました。
Notion AIへの依頼プロンプト
作成されたページ上でNotionAIに以下の依頼プロントでレポートさせます。
あなたはプロジェクトマネージャの片腕として、このページのメッセージを分析し、
以下の観点でプロジェクトマネージャ向けにレポートを作成してください:
・発生事象(何が起きたか、誰が関与しているか)
・課題・懸念点(技術的・業務的な問題、未解決の点)
・ToDo・アクション項目(誰からの依頼で誰が何をすべきか、期限があるか)
・システムアラート・障害(エラー通知、監視アラート、対応状況)
・不明点やフォローアップすべきこと
・直近の予定
・その他、共有すべき重要な情報
レポートは、簡潔かつ明確に、関係者がすぐに理解・対応できるようにまとめてください。
また、必要に応じてチャンネル名・日時・発言者を引用してください。
レポート例
Notion AIで生成されたレポートのサンプルの一部です。

レポートを活用
レポート結果をNotionのページに保存→朝会・夕会の準備やフォローアップに活用します。
まとめ:ITの民主化はもう始まっている
生成AIとAPIの進化により、「ちょっとした業務改善」や「試してみたいアイデア」が、専門知識なしでも形にできる時代が到来しました。
バイブコーディングは、そんな時代における新しい開発スタイルであり、現場のPMや非エンジニアが自分の手でIT化を進めるための武器です。
今回紹介したSlack要約ツールのように、小さく作ってすぐ使える仕組みは、日々の業務に確かな変化をもたらします。
そしてその積み重ねが、組織全体の情報活用力や意思決定のスピードを底上げしていくでしょう。
「自分にもできるかも」と思った方は、ぜひ一度バイブコーディングを試してみてください。
Discussion