2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Notionのメモをもとに記事を作らせて投稿してみる

Last updated at Posted at 2024-01-13

これははてなエンジニア Advent Calendar 2023の2024 年 1 月 13 日の記事です。
昨日 id:mangano-itoさんのWear OS スマウォでスマホと連携する単語帳アプリをつくってみよう でした。

Wear OS! 全体感の図が分かりやすくて必見ですね。

私は自分のNotionメモに

  • WearOSで、タップしたら出退勤してくれるボタン
  • WearOSで、音声入れたらよしなに家計簿入力してくれる
  • WearOSで、心拍数が一定を超えたらもっと頑張れって煽ってくれる

...
とかやりたいことだけ書いてありますが、途中で飽きて逃亡してます。

本編

日々の情報収集、日記、TODOやちょっとした思いつきなどあらゆるメモをNotionに書いてます。
今回、そのメモから記事を作って投稿する ということをやってみました。
投稿した記事 👇

AIが書く最初と最後の枕詞的な文章は消して、あとは導入だけ自分で書いてます。文章感がガラッと変わるので面白みありつつこの辺も調整したい。

使ったサービス

Notion内には、IntergrationというNotion内での特定のアクションをトリガーにプログラムを動かすってことができるっぽいのですが、今回は普通にAPI使って記事を取得しただけです。


Assistans API beta を使ってます。Code Interpreterが使えて、事前に与えるプロンプトにファイルが使えます。



使用言語はKotlinです。

(ReadMe後ほど書きます。。)

何をやったか

シンプルですね。とにかくワンショットで投稿までいってるように見えます

Notion周り

NotionはMarkDownではない

Notionは MarkDownの記法以外にも独自の属性を持った文字があって、ここ のtype 分に属性があります。


Qiitaに投稿するにあたってMarkDownがあれば十分だけど、NotionにMarKDonw形式で返してくれる機能はないので、NotionAPIをいい感じにラップしたライブラリを使ってます。

サンプル

const { Client } = require("@notionhq/client");
const { NotionToMarkdown } = require("notion-to-md");
const fs = require('fs');
// or
// import {NotionToMarkdown} from "notion-to-md";

const notion = new Client({
  auth: "your integration token",
});

// passing notion client to the option
const n2m = new NotionToMarkdown({ notionClient: notion });

(async () => {
  const mdblocks = await n2m.pageToMarkdown("target_page_id");
  const mdString = n2m.toMarkdownString(mdblocks);
  console.log(mdString.parent);
})();

Notion側のページのIDだけ指定すればよしなにとってきてくれるので便利。JSをkotlinでラップする暇はなかったのでローカルにサーバー立ててそこと通信する形をとりました。

どうやってページを持ってくるか

(人によるかも)リレーションで作ったタグをメモにつけて、1つのデータベースでそのメモ群を管理してるので

そのタグがくっついてるページをとってきてます。

// client/src/main/kotlin/processorImpl
val pageData = notion.retrievePageIds().bind()
val pageId = pageData.firstPage().bind()
val byteMarkDown = notion.getMarkDownContent(pageId).bind()
// ... OpenAIとかQiitaとか
notion.deleteTag(qiitaTagRelation).bind()
  • データベースIDからタグ付きページIDとってくる
  • ページIDから中身の文章取ってくる
  • 諸々終わったらタグを消す

1つのデータベースに全てのページをつっこむ管理方法だから割とシンプルに行けてます。(逆に複数のデータベースがあると大変そう)

前述でちょっと書いてますが、Notion上にアプリを乗っけるやり方が良さそうではあるのですが、今回はそこまで行けず。。

OpenAI周り

プロンプト

このアシスタントの目的は、Markdown形式で書かれたメモを技術記事に変換することです。以下を守りつつ、再構成した記事を書いてください。形式はマークダウンとします。

- 入力はマークダウンファイルとして渡されます。
- メモの核となるアイデアを把握し、わかりやすさのために内容を再構成します。
- 技術的な詳細とコードスニペットは正確に保ち、理解可能で実用的なものに焦点を当てます。
- 組織化のために見出しを使用しますが、多用は避けます。シンプルで魅力的な導入部だけで読者を引き込むのに十分です。
- メモの疑問や問題に対しては、簡潔な説明や迅速なリサーチを行ってください。
- 最終的な記事は、技術的に正確でありながら親しみやすく、メモの元のキャラクターとアプローチャブルさを保持するものであるべきです。
- 再構成した記事のみを返却してください。
- 使用言語は日本語としてください。

(まあ、これもGPTに作ってもらったんですが)
これ + 今まで書いた記事をいくつかファイルで渡してます。<- こっちはあまり意味を感じてない

あとは、OpenAIのクライアントはKotlin製のライブラリがあったりします

val fileId = client.uploadFile(byteString).bind()
client.createMessage(fileId).bind()
val runId = client.run().bind()
client.retrieveRun(runId).orElseIntervalActionAsync(
        predicate = { error -> error is OpenAiError.RunningStatusNotComplete },
        action = { client.retrieveRun(runId) },
        interval = 30000L, // 最大3min
        retryCount = 5
    ).bind()
val message = client.retrieveMessage().bind()
message.text.value

AIが生成するまで1~2分かかるので、インターバルを設けてステータスを確認しにいってます。

Qiita

これはPOSTだけ使ってます。特に書くことはなさそう

久々に昔作ったBotで動かしたら、private フラグ立てるの忘れててtestが公開されたミスをしました

private
限定共有状態かどうかを表すフラグ (Qiita Teamでは無効)
Example: false
Type: boolean

Kotlin-Resultが便利 という話

👇のkotlin-result。最近使ってなかったですがやっぱり便利。

いわゆるモナドな文脈 が作れるのでエラー気にしなくてよかったり、ネストがなくなったり

val sum: Result<Int, DomainError> = binding {
    val x = functionX().bind()
    val y = functionY().bind()
    val z = functionZ().bind()
    x + y + z
}

インターバル付きのリカバリー処理が orElseの連鎖だけで 書きやすい。

suspend fun <T, E>  MResult<T, E>.orElseIntervalActionAsync(
    action: suspend () -> MResult<T, E>,
    retryCount: Int = 2,
    interval: Long = 1000L,
    predicate: (E) -> Boolean = { true }
): MResult<T, E> {
    return this.orElse { error ->
        if (retryCount > 0 && predicate(error)) {
            delay(interval)
            action().orElseIntervalActionAsync(action, retryCount - 1, interval, predicate)
        } else {
            this
        }
    }
}

OpenAIの文章生成待つのに、インターバルかけて何度かリクエスト送るのに使ってました。

そのうちやりたいという意気込みだけある

  • NotionのIntergrationでNotion上にアプリ乗っける
  • Fine-tuningでもっと自分っぽい言葉遣いな記事にする
  • 画像に対応しなければ、、
  • open interpreter で野良のローカルなモデルで動かす
  • KMPのJSで、NotionAPIをラップしてるJSライブラリをラップする
  • (そもそもNotion側にもAIがいるので最初から記事にしちゃえばいいのでは)

明日は、id:papix さんです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?