こんにちは、コミューンでプロダクトマネジャーをしているひぐです。
この記事ではノーコード自動化ツールの”n8n“を使って、AI系のニュースを収集・要約するbotを開発した経験を共有します。
こんな感じで週次でLinearなどのテック企業、Y combinator,zennのブログなどから、関心のありそうなものをピックアップしてdiscordでサマリーしてくれます。
情報のキュレーションはよくあるツールです。代替もありますが、以下の理由で自作しました。
- 最近のノーコードツールでどれくらいのことができるか知りたかった
- サマリの文体やデータソース、アウトプット先など細かいところをチューニングしたかった
- ノーコードツールに詳しくなり、業務効率改善できる範囲を増やしたかった
このパイプラインの設定値が記載されたjsonとデプロイ用のコードはGithubに公開しているので良ければ参照ください🙏
n8nとは?
n8nは、様々なWebサービスやAPIをGUI上でつなぎ合わせ、ワークフローを自動化できるオープンソースのツールです。
ZapierやIFTTTと似ていますが、条件分岐や繰り返し処理など複雑なロジックを組んだり、セルフホストして自由にカスタマイズできる点が特徴です。
クラウド版があり、月24€(4100円くらい)でホスティングなども行ってくれます。
コミュニティ版は自分でホスティング、データのストレージなどをする必要がありますが、無料で利用できます。今回はこれを利用しました。
また、後述しますが、GUI上で構築したワークフローをjsonで保存したり、コミュニティに公開することができます。このような機能があることで、既存のワークフローを改修して使い始めるのが非常に簡単です。👍
仕組みを作る上で大切にしたこと
このBotを自分自身のメインの情報源として長く使いたかったので、設計段階で以下の点を意識しました。
- 一つのメディアに依存せず、国内外のブログ、ニュースレター、コミュニティなど、様々な情報源を簡単に追加・変更できること。
- 生成したサマリーをDiscordだけでなく、将来的にはメールやNotionなど、様々な場所に届けられる拡張性を持つこと。
- 個人のプロジェクトとして、無理なく継続的に運用できる安価なインフラを構築すること。
パイプラインの全体像
上記のような要件を元にパイプラインを生成しました。
パイプラインは、大きく分けて「データ投入」と「サマリー生成」の2つのワークフローで構成されています。
ベースは下記のテンプレートを利用しています。
今回はn8n自体にBASIC認証をかけていますが、Supabase側で「Network Restrictions」を設定し、指定したIPアドレスからしかデータベースにアクセスできないようにすることをおすすめします。
データをDBに投入するパート
大きく分けると下記のパーツに分かれています。
- 記事収集 (日次): スケジュール実行で、あらかじめ登録した複数のRSSフィードから記事を自動で取得する。
- データ正規化: 取得した記事のデータ構造(タイトル、URL、本文など)を共通のスキーマに整形する。
- Embeddingと保存: 整形した記事本文をチャンクに分割し、ベクトル化してVector Storeに保存する。
サマリを生成してDiscordに送るパート
サマリ生成から、Discordに送るパートの処理は下記の通りです。
- 記事検索 (週次): 別のスケジュール実行で、「AI」などの指定したトピックをクエリとしてVector Storeを検索し、関連性の高い記事チャンクを取得する。
- リランキング: 取得した記事を、よりトピックとの関連性が高い順に並べ替える
- 要約と構造化: 上位5件の記事について、LLMに要約を指示。その際、単なる文章ではなく、Structured OutputとしてJSON形式で出力させる。
- テキスト生成: 生成されたJSONデータから、Discordで読みやすいMarkdown形式のテキストを組み立てる。
- 分割と配信: 完成したテキストがDiscordの文字数制限(2000文字)を超える場合に備え、チャンクに分割し、複数メッセージとしてDiscordチャンネルに配信する。
パイプラインの詳細
技術スタックとインフラ構成
低コストと開発しやすさを両立させるため、以下の技術スタックを選択しました。
データベース: Supabase (PostgreSQL)
無料枠が豊富で、セットアップも非常に簡単で、ベクトルデータを扱うためのpgvector拡張も標準でサポートされており、今回の用途に最適でした。
公式ドキュメントでインテグレートが詳しく説明されています。
デプロイ先: Google Cloud Run
アクセスがない時間はコストがほぼゼロになるため、個人用途での運用に向いていると思い選択しました。個人的に使い慣れている点も大きいです。
デプロイフロー
n8nの公式のDockerfileをGoogleのArtifact Registryにプッシュ。そこからCloud Runにデプロイしています。
データベースの設定
今回はCloud Runで立ち上げたn8nの管理者やワークフローの情報を永続化することと、RAGのデータを貯めるためにsupabaseを利用しています。
後者のn8n経由でRAGのデータを貯めるには、pgvectorというPostgresでベクトルデータを保存できる拡張機能を利用しています。
n8n側で利用するにはこの要件でテーブルを作成し、GUI上で選択する必要があります。
テーブル生成用のクエリ
DROP TABLE IF EXISTS documents CASCADE;
DROP FUNCTION IF EXISTS match_documents;
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
metadata JSONB,
embedding VECTOR(768)
);
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
CREATE OR REPLACE FUNCTION match_documents (
query_embedding VECTOR(768),
match_count INT DEFAULT NULL,
filter JSONB DEFAULT '{}'
) RETURNS TABLE (
id BIGINT,
content TEXT,
metadata JSONB,
similarity FLOAT
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
id,
content,
metadata,
1 - (documents.embedding query_embedding) AS similarity
FROM documents
WHERE metadata @> filter
ORDER BY documents.embedding query_embedding
LIMIT match_count;
END;
$$;
プロンプト
今回は、記事を読み込ませ、それぞれ同じフォーマットでサマリーを生成するタスクになります。
AI AgentにToolsとしてVector SearchできるDBを持たせるので、そのようなツールを所持していることをAI側に明示し、それを使って記事を取得してから、サマリーを生成させることを指示させています。
サマリ生成のプロンプト
あなたは、提供された技術記事を分析し、その内容と重要性を、多忙なプロフェッショナル向けに分かりやすく解説する「専門技術アナリスト兼エディター」です。
あなたの仕事は、以下のステップで構成されます。
-
情報検索: まず、ユーザーの興味関心である「{{トピック}}」に関連する情報を、あなたが利用できるVector Store検索ツールを使って検索し、関連性の高い記事を複数件取得してください。
-
個別理解と要約: 次に、取得した各記事を精読し、以下の要件に従って個別に要約を作成します。
- 背景と目的の把握: 「なぜこの記事が書かれたのか」「解決しようとしている課題は何か」を理解します。
- 核心部分の抽出: 記事の最も重要な主張、提案、または発見を特定します。
- 具体例の認識: 主張を支える具体的な技術名、製品名、数値データ、手法などを正確に抜き出します。
-
要点の文章化:
key_points
を作成する際は、単語やフレーズをリストアップするのではなく、それぞれの要点が何を意味するのかが分かる、自己完結した完全な文章として記述してください。
-
重要度評価: ユーザーの興味関心に基づき、各記事の「新規性」「実用性」「影響の大きさ」といった観点から重要度を評価・ランク付けしてください。
-
トップ5の選抜とJSON生成: 最も重要かつだと判断した上位5つの記事だけを選び抜き、指定されたJSONフォーマットで出力してください。更に、最新かつ多様なデータソースから取得するように意識してください。
- あなたの最終的な出力は、必ず指定された構造のJSONオブジェクトのみとしてください。JSONの前後に説明文などの余計なテキストを含めないでください。
- JSONのルートオブジェクトは
summaries
というキーを持ち、その値は記事要約オブジェクトの配列とします。 - 配列の各要素(記事要約オブジェクト)は、以下のキーを持つ必要があります。
-
rank
: (数値) 記事の重要度ランキング (1から5) -
title
: (文字列) 記事の正確なタイトル -
url
: (文字列) 記事の元URL -
gist
: (文字列) 約300文字程度で、記事の背景、核心、結論をまとめた「詳細な要約」 -
key_points
: (文字列の配列) 記事から抽出した、具体的な技術名、数値、手法などを含む要点を、それぞれが独立した文章として完結する形で3〜5個
-
Userprompt↓
あなたが持つ「熟練の編集者兼リサーチャー」の能力を最大限に活かし、指定された出力フォーマットに従って要約を作成してください。Vectore Storeを使って記事を検索してください。検索するキーワードは {{ $json.Topic }} です
n8nの良いところ
この仕組みを作るにあたって、n8nはかなり便利だなーと感じました!
今回のステップは、「データ取得 → リランキング → 要約 → 構造化出力」という複数のステップを実行しています。n8nのAgentノードやLLMノードを組み合わせることで、引数の型やAPIの呼び出しといった詳細をあまり意識せずに実装できました。
例えば、RAGをToolとして持ったAI AgentにSturcutred Outputさせる、というやや複雑なことも
Agentノードに生えているChatModel, Tool, Output Parserなどに適切なノードを繋げて、プロンプトを記載すれば簡単に実現できます。
Agent側の設定
Agentとつながっているノード
また、公開されているテンプレートを改修して始められたのも嬉しい点でした。
また、ノードをコピーすると、その設定がJSONとしてクリップボードに保存されるのが非常に便利でした。ローカルで開発した一部の処理をリモート環境にコピペしたり、設定JSONをそのままGeminiに渡して「この設定どう思う?」と相談したりする、といった使い方ができます
難しかったところ
今回はいわゆるRAGを使っていますが、トピックに対して関連する投稿を取ってくる、というだけでもかなり考える必要があると感じました。
記事をどのくらいの大きさで、どの程度重ねて分割するのが最適か、チャンクサイズを決めたり、Vector Storeから何件の記事を取得し、どうリランキングすれば、最終的なアウトプットの質が上がるかなど考えないといけない変数が多いです。
また、実運用に至るまでには、いくつかの微調整が必要でした。例えば、RSSサイトで取得できるフォーマットは微妙に異なるので、これを共通化し、サーチしやすい形にDBに詰める、WebhookからDiscordに送るには2000文字の制限があるので、生成結果を切ってから送る、といったことが挙げられます。
また、今回はコストを抑えるためにセルフホストを選択しました。Cloud Runからsupabaseにアクセスできるようにするためのポート設定や、環境変数の設定など、細かい点で詰まりました…
まとめ
n8nでAIニュース要約Bot」を自作してDiscordに流すまで、について紹介しました。
良質な記事をより精度高く取得するためには、過去に取得した記事との重複を排除したり、Vector StoreへのEmbedding方法を工夫したりする必要があります。今後チューニングする際には下記のブログなどを参考にしたいです 🙏
n8nをセルフホストできる強力な基盤が手に入ったので、今後は様々な自動化に挑戦したいと考えています。例えば、下記の資料のようなGitHubと連携して「今週デプロイされた変更内容のサマリー」を生成したり、Notionのデータベースを読み込ませて「新メンバー向けのオンボーディング支援Bot」を開発したいです。
では〜!
参考資料
Views: 0