Skip to content

Instantly share code, notes, and snippets.

@ayakix
Last active February 25, 2026 12:37
Show Gist options
  • Select an option

  • Save ayakix/125af48cabd9d2ace828db448e3baee9 to your computer and use it in GitHub Desktop.

Select an option

Save ayakix/125af48cabd9d2ace828db448e3baee9 to your computer and use it in GitHub Desktop.
Personal Intelligence Pipeline (PIP) 実装プラン

Personal Intelligence Pipeline (PIP) 実装プラン

概要

スマホからURLを共有 → GitHub Issue作成(ラベル: pip)→ GitHub Actionsが即時処理 → AI要約をMarkdownで articles/ に保存 → Slack通知 → Obsidianで閲覧。

コスト: $0(全て無料枠内で運用)


アーキテクチャ

スマホ: iOSショートカット(URL共有)
  → GitHub Issue 作成(ラベル: pip, bodyにURL)
       ↓ issues:opened トリガー
GitHub Actions: 即時実行
  ├── ラベル "pip" チェック(なければスキップ)
  ├── Issue bodyからURL取得(環境変数経由で安全に抽出)
  ├── 重複URL検出(既存記事と同一URLならスキップ)
  ├── trafilatura で本文抽出
  ├── Gemini 2.5 Flash Lite で要約(REST API, JSON出力)
  ├── Markdown生成 → articles/ に commit & push
  ├── Slack通知(Incoming Webhook)
  └── Issue close

Daily Cron:
  └── articles/ で自動整理
      90日経過 → 削除
       ↓
Obsidian Git同期 → 閲覧・検索

技術スタック

レイヤー 技術 コスト
URL入力 iOSショートカット → GitHub Issue API $0
ワークフロー GitHub Actions (issues:opened + daily cron) $0(月2000分無料枠)
本文抽出 trafilatura(Python OSS) $0
AI要約 Gemini 2.5 Flash Lite REST API(requestsで直接呼び出し) $0
記事保存 リポジトリの articles/(Markdown) $0
通知 Slack Incoming Webhook $0
閲覧 Obsidian + Git同期プラグイン $0

Gemini呼び出しについて: google-genai 等のSDKは使わず、requests ライブラリで REST API を直接呼び出す。responseMimeType: application/json + responseSchema でJSON出力を強制する。スキーマの型名は大文字(STRING, INTEGER, OBJECT, ARRAY)を使用する(REST API仕様)。

リポジトリ構成

<your-repo>/
  process_article.py           # メインスクリプト(記事処理)
  cleanup_articles.py          # 自動整理スクリプト
  pyproject.toml               # Python依存管理
  Inbox.md                     # Obsidian DataView: 過去7日間の記事一覧
  Important.md                 # Obsidian DataView: 過去30日間の重要記事一覧(importance = 3)
  terraform/                   # GCPリソース管理(API有効化のみ)
    main.tf
    variables.tf
  articles/                    # 記事Markdown保存先
.github/workflows/
  pip_process.yml              # Issue trigger + workflow_dispatch: 記事処理
  pip_cleanup.yml              # Daily cron: 自動整理

認証・シークレット

シークレット 用途 登録先
GEMINI_API_KEY Gemini API 呼び出し GitHub Secrets
SLACK_WEBHOOK_URL Slack通知 GitHub Secrets
  • ワークフロー: リポジトリへの commit & push はデフォルトの GITHUB_TOKENcontents: write permission)で可能。追加PATは不要。
  • iOSショートカット: リポジトリへのIssue作成用に Fine-grained PAT を1つ発行。

Fine-grained PAT(iOSショートカット用)

  • Repository access: 対象リポジトリのみ
  • Permissions: Issues (Read and write) のみ

Phase 0: 事前準備

0.1 GCPプロジェクト作成

  • GCPプロジェクト作成

    gcloud projects create <your-project-id>
  • ADC認証

    gcloud auth application-default login

0.2 Terraform によるAPI有効化

TerraformではAPI有効化のみ管理する。APIキーはgcloudで作成する。

  • terraform/main.tf 作成

    terraform {
      required_version = ">= 1.0"
      required_providers {
        google = {
          source  = "hashicorp/google"
          version = "~> 6.0"
        }
      }
    }
    
    provider "google" {
      project = var.gcp_project_id
    }
    
    resource "google_project_service" "generative_language" {
      service            = "generativelanguage.googleapis.com"
      disable_on_destroy = false
    }
    
    resource "google_project_service" "apikeys" {
      service            = "apikeys.googleapis.com"
      disable_on_destroy = false
    }
  • terraform/variables.tf 作成

    variable "gcp_project_id" {
      description = "GCPプロジェクトID"
      type        = string
    }
  • .gitignore に追加

    terraform/.terraform/
    terraform/*.tfstate*
    terraform/terraform.tfvars
    
  • Terraform 実行

    cd terraform
    echo 'gcp_project_id = "<your-project-id>"' > terraform.tfvars
    terraform init
    terraform plan
    terraform apply

0.3 APIキー作成(gcloud)

  • APIキー作成

    gcloud services api-keys create \
      --display-name="PIP - Gemini API" \
      --api-target=service=generativelanguage.googleapis.com \
      --project=<your-project-id>
  • APIキー取得

    # 作成されたキーのUID(出力に含まれる)を指定
    gcloud services api-keys get-key-string <KEY_UID> --project=<your-project-id>
  • GitHub Secrets に GEMINI_API_KEY を登録

0.4 その他の事前準備

  • Slack Incoming Webhook 作成

    • 通知用チャンネルを決めて作成
    • GitHub Secrets の SLACK_WEBHOOK_URL に登録
  • pip ラベル作成

    • リポジトリに pip ラベルを追加
  • articles/.gitkeep 作成


Phase 1: メインスクリプト実装

process_article.py — URL → 要約 → Markdown生成 → 保存 → 通知

1.1 プロジェクト設定

  • pyproject.toml 作成

    [project]
    name = "pip"
    version = "0.1.0"
    requires-python = ">=3.10"
    dependencies = [
        "requests",
        "trafilatura",
    ]
  • uv lock 実行

1.2 本文抽出

  • extract_content(url: str) -> dict 実装
    • trafilatura で本文抽出
    • 戻り値: { "title": str, "body": str, "source_url": str }
    • 抽出失敗時: body を空文字にし、タイトルはURLのドメインで代替

1.3 AI要約

  • Gemini プロンプト定義

    • 自分の興味・関心に基づくペルソナを定義し、記事を多角的に評価
    • ペルソナ例:
      • ソフトウェアエンジニア: 使用言語、クラウド、AI/LLM、アーキテクチャ等
      • 個人投資家: 株式、経済指標、マクロ経済等
      • その他、自分の趣味・関心事
    • JSON出力指定:
      {
        "title": "記事タイトル(日本語以外は日本語に翻訳)",
        "abstract": "100文字程度の要約",
        "importance": 3,
        "category": "tech",
        "insights": "パーソナライズされた洞察",
        "summary": "記事全体の詳細な要約",
        "tags": ["キーワード1", "キーワード2"]
      }
    • importance基準(1-3の3段階):
      • 3: 必ず読むべき(直接的な業務影響、大きな機会、重要なトレンド)
      • 2: 時間があれば読む(一般的に有用な情報、間接的に関連)
      • 1: 自分との関連性が低い
    • category: 自分の関心領域に合わせて定義(例: tech, investment, hobby, other 等)
  • summarize_article(title: str, body: str, url: str) -> dict 実装

    • Gemini 2.5 Flash Lite REST API を requests で直接呼び出し
    • エンドポイント: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent
    • system_instruction でシステムプロンプト、generationConfigresponseMimeType + responseSchema を指定
    • JSONレスポンスのパース(失敗時はデフォルト値でフォールバック)
    • 本文が長すぎる場合は200,000文字で切り詰め
    • importanceは max(1, min(3, ...)) でバリデーション

1.4 Markdown生成

  • generate_markdown(article: dict, content: str) -> str 実装

    • テンプレート:
      ---
      url: {url}
      title: "{title}"
      importance: {importance}
      category: {category}
      tags: [{tags}]
      created: {YYYY-MM-DDTHH:MM}
      ---
      
      ## Abstract
      {abstract}
      
      ## Insights
      {insights}
      
      ## Summary
      {summary}
      
      ## Original
      {取得テキスト(5000文字で切り詰め、超過時は「(以下省略)」を付与)}
  • ファイル名生成

    • フォーマット: YYYYMMDD_タイトル.md
    • サニタイズ: 特殊文字除去(/, \, :, *, ?, ", <, >, |
    • 長さ制限: タイトル部分150文字以内に切り詰め

1.5 重複URL検出

  • is_duplicate(url: str) -> bool 実装
    • articles/ 配下の既存 .md ファイルのfrontmatter url: を走査
    • 同一URLが存在すれば True を返す
    • 重複時はGemini API呼び出しも含めスキップ(コスト・処理時間の節約)

1.6 GitHub保存

  • save_article(filename: str, content: str) 実装
    • articles/{filename} にファイル書き込み
    • os.makedirs(exist_ok=True) でディレクトリ自動作成
    • git commit & push はワークフロー側で実行

1.7 Slack通知

  • notify_slack(article: dict) 実装
    • Incoming Webhook で Block Kit 送信
    • ヘッダー: カテゴリ別アイコン + タイトル
    • 本文: 要約 + 重要度(★3段階表示)
    • タグ: バッククォート付きで表示
    • リンク: 元記事URLボタン
    • SLACK_WEBHOOK_URL 未設定時はスキップ

1.8 メイン処理

  • main() 実装
    • 環境変数 TARGET_URL からURL取得
    • urlparse でスキーム検証(http/https のみ許可)
    • 重複URL検出 → extract → summarize → generate_markdown → save → notify → GITHUB_OUTPUT出力 の順で実行
    • GITHUB_OUTPUT: 記事タイトルを article_title として出力(ワークフローのコミットメッセージ用)

Phase 2: GitHub Actions ワークフロー

2.1 記事処理ワークフロー

  • .github/workflows/pip_process.yml 作成

    name: PIP Process
    on:
      issues:
        types: [opened, reopened]
      workflow_dispatch:
        inputs:
          url:
            description: '処理するURL'
            required: true
            type: string
    permissions:
      contents: write
      issues: write
    jobs:
      process:
        if: >-
          (github.event_name == 'workflow_dispatch') ||
          (github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'pip'))
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Install uv
            uses: astral-sh/setup-uv@v5
          - name: Extract URL
            id: extract
            env:
              ISSUE_BODY: ${{ github.event.issue.body || '' }}
              DISPATCH_URL: ${{ github.event.inputs.url || '' }}
            run: |
              if [ -n "$DISPATCH_URL" ]; then
                echo "url=$DISPATCH_URL" >> "$GITHUB_OUTPUT"
              else
                URL=$(echo "$ISSUE_BODY" | grep -oP 'https?://\S+' | head -1)
                echo "url=$URL" >> "$GITHUB_OUTPUT"
              fi
          - name: Validate URL
            run: |
              URL="${{ steps.extract.outputs.url }}"
              if [ -z "$URL" ]; then
                echo "ERROR: URLが取得できませんでした"
                exit 1
              fi
              echo "処理対象URL: $URL"
          - name: Process article
            id: process
            env:
              TARGET_URL: ${{ steps.extract.outputs.url }}
              GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
              SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
            run: uv run process_article.py
          - name: Commit and push article
            run: |
              git config user.name "github-actions[bot]"
              git config user.email "github-actions[bot]@users.noreply.github.com"
              git add articles/
              if git diff --cached --quiet; then
                echo "変更なし"
              else
                TITLE="${{ steps.process.outputs.article_title }}"
                if [ -z "$TITLE" ]; then
                  TITLE="new article"
                fi
                git commit -m "add: $TITLE"
                git push
              fi
          - name: Close issue
            if: github.event_name == 'issues'
            env:
              GH_TOKEN: ${{ github.token }}
            run: gh issue close ${{ github.event.issue.number }} --repo ${{ github.repository }}

    ポイント:

    • Issue bodyのURL抽出は環境変数経由(${{ }} を直接shellに展開しない)でインジェクション対策
    • スクリプト失敗時は Close issue ステップに到達しないため、Issueはopenのまま(reopenで再実行可能)
    • workflow_dispatch でURL直接指定の手動実行もサポート

2.2 自動整理ワークフロー

  • cleanup_articles.py 実装

    • frontmatter パース(簡易regex: --- で囲まれたYAML部分を抽出)
    • created フィールドを読み取り(YYYY-MM-DDTHH:MM 形式)
    • 条件: 90日経過 → 削除(os.remove
    • 削除件数のログ出力
  • .github/workflows/pip_cleanup.yml 作成

    name: PIP Cleanup
    on:
      schedule:
        - cron: '0 19 * * *'  # 毎日 UTC 19:00(JST 04:00 等、自分のタイムゾーンに合わせて調整)
      workflow_dispatch:
    permissions:
      contents: write
    jobs:
      cleanup:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Install uv
            uses: astral-sh/setup-uv@v5
          - name: Run cleanup
            run: uv run cleanup_articles.py
          - name: Commit and push changes
            run: |
              git config user.name "github-actions[bot]"
              git config user.email "github-actions[bot]@users.noreply.github.com"
              git add articles/
              if git diff --cached --quiet; then
                echo "変更なし"
              else
                git commit -m "chore: cleanup old articles"
                git push
              fi

Phase 3: iOSショートカット

  • Fine-grained PAT 発行

    • Repository: 対象リポジトリのみ
    • Permissions: Issues (Read and write) のみ
  • ショートカット作成

    • 名前: 「PIPに保存」
    • トリガー: Share Sheet(URL受付)
    • 処理:
      1. 入力からURLを取得
      2. GitHub API呼び出し:
        POST https://api.github.com/repos/<owner>/<repo>/issues
        Headers:
          Authorization: Bearer {PAT}
          Content-Type: application/json
        Body:
          {
            "title": "pip: {URL}",
            "body": "{URL}",
            "labels": ["pip"]
          }
        
      3. 完了通知: 「PIPに送信しました」
  • 動作テスト

    • Safariの共有メニューからショートカット実行
    • Issue作成 → Actions起動 → Slack通知の一連を確認

Phase 4: Obsidian設定

  • リポジトリをObsidian Vaultとして開く

  • Obsidian Gitプラグイン設定

    • 自動Pull間隔: 5分
  • Dataview プラグイン設定

    • Inbox.md(過去7日間の記事一覧):
      TABLE importance AS "重要度", category AS "分類", tags AS "タグ"
      FROM "articles"
      WHERE date(created) >= date(now) - dur(7 days)
      SORT created DESC
      
    • Important.md(過去30日間の重要記事一覧):
      TABLE category AS "分類", tags AS "タグ"
      FROM "articles"
      WHERE importance = 3
        AND date(created) >= date(now) - dur(30 days)
      SORT created DESC
      
  • 動作確認


Phase 5: テスト・運用開始

  • E2Eテスト: メインパイプライン

    • pip ラベル付きIssueを手動作成
    • GitHub Actions が起動し、処理が完了すること
    • articles/ に Markdownが保存されること
    • Slack通知が届くこと
    • Issueがcloseされること
  • E2Eテスト: エラーケース

    • 無効なURL → エラー通知、Issueはopenのまま
    • trafilatura抽出失敗 → タイトル・URLのみで保存(フォールバック)
    • Gemini APIエラー → デフォルト値で保存
    • pip ラベルなしのIssue → スキップされること
    • 重複URL → スキップされること
  • E2Eテスト: 自動整理

    • テスト用ファイル(created: 91日前)を手動作成
    • workflow_dispatch で手動実行
    • 90日経過した記事が削除されること
    • 90日未満の記事は残ること
  • iOSショートカットからのE2Eテスト

  • Obsidian同期テスト


実装順序

Phase 0 (事前準備)        ← GCPプロジェクト + Terraform + gcloud + Secrets登録
  ↓
Phase 1 (スクリプト実装)   ← コア処理。ローカルで動作確認可能
  ↓
Phase 2 (GitHub Actions)  ← Issue → 自動処理が動くようになる
  ↓
Phase 3 (iOSショートカット) ← スマホから使えるようになる
  ↓
Phase 4 (Obsidian)         ← 閲覧環境の整備
  ↓
Phase 5 (テスト)           ← 全体の動作確認

事前に準備すること

# 作業 場所
1 GCP認証(gcloud auth application-default login ターミナル
2 GCPプロジェクト作成(gcloud projects create <your-project-id> ターミナル
3 Slack Incoming Webhook 作成 → SLACK_WEBHOOK_URL 登録 Slack + GitHub Secrets
4 pip ラベル作成 リポジトリ Issues > Labels
5 Gemini APIキー作成(gcloud) → GEMINI_API_KEY 登録 ターミナル + GitHub Secrets
6 Fine-grained PAT 発行(Phase 3で使用) GitHub Settings > Developer settings

カスタマイズポイント

このプランは汎用テンプレートです。以下を自分の用途に合わせて調整してください。

項目 説明
ペルソナ システムプロンプトの評価ペルソナを自分の興味・職種に合わせて定義
カテゴリ category の選択肢を自分の関心領域に合わせて変更
重要度基準 importance 1-3 の判定基準をペルソナに合わせて調整
自動整理期間 90日は一例。保存期間は好みで変更可能
cron時刻 タイムゾーンに合わせてcronスケジュールを調整
通知先 Slack以外(Discord, LINE等)にも変更可能
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment