Jev を使って自然言語で検索するMovable Typeプラグイン

  • 投稿日:
  • by
  • カテゴリ:

これは何か?

Movable Typeの管理画面の検索で「自然言語で検索」を選択できるようにするプラグインです。

OpenAI の text-embedding-3-large で記事やコンテンツデータをベクトル化して、先にベクトル検索で絞り込んだうえで、その結果から Jev を使って自然言語でのフィルタリングとリランキングを行う形で実現しています。(ベクトルは既存のDBに保存し、類似度の計算はPerl側で行っています。数千件程度のデータであれば実用になりそうなので、いまはここまでにしていますが、さらに件数が増える場合にはベクトル検索に対応したDBを使うなどの工夫が必要になると思われます。)

Jev への入力トークンは若干大きめになっているので、らしさを十分に引き出せているかどうかは微妙なところはあるものの、それでも追加学習なしで、低コストかつ高速(そして割と一貫性のある結果)でフィルタリングとリランキングを実現できていて、まあまあ実用的なものになっている気がします。

GitHub - usualoma/mt-plugin-jev: Natural-language search for Movable Type 9 with OpenAI embeddings and Jev.
Natural-language search for Movable Type 9 with OpenAI embeddings and Jev. - usualoma/mt-plugin-jev
github.com

動作デモ

とあるアニメーションの感想記事(内容は全てダミー)を入れて、プラグインを使って検索したときのデモが以下になります。

  • 映画/テレビアニメ:各50件
  • ネタバレあり/なし:各50件
  • 肯定的/否定的:各50件

「直接のキーワード指定」や「専用の構文」を使うことなしに、検索ができていることが分かります。

実装の概要

最初の段階では、検索のたびに全ての記事を Jev に渡して条件を満たすかどうかを判定する方法を試してみて、結果は期待したものになってはいました。しかし 1記事5,000トークンとして、5,000記事を評価すると、検索1回で$1というコストになり、それは実験的なプラグインとはいえちょっとコストが嵩みすぎるという感じがしたので、ベクトル検索で先に絞り込むことにしました。

記事ごとのベクトルは、データから MT で検索対象となる項目を取り出し、HTML を取り除き、項目名と値をまとめた JSON を text-embedding-3-large に渡して生成します。(それ以上の細かい分割や要約は行わない)

検索時には、入力された自然文も同じモデルでベクトル化します。検索対象の保存済みベクトルを100件ずつまとめて取得し、検索文とのコサイン類似度を Perl で計算していく感じです。(とても素朴な実装)

ベクトル検索で上位50件を抽出し、この候補を Jev に渡してそれぞれについて2種類の質問をします。

  • Noul:検索条件を満たしているか。0〜1の値を返してもらう。
  • Score:探している情報との関連度はどのくらいか。0〜4の値を返してもらう。

結果を受け取ったら、Noul がしきい値以上のものだけを残し、Score の高い順に並べます。

Jev への送信は、入力サイズの制限と、判定に関係のない情報が増えることによる精度への影響を考慮して、デフォルトで5件ずつにしています。入力サイズの制限に収まれば、候補50件を10リクエストに分けて処理することになります。この API の呼び出しはパフォーマンスのボトルネックになるので、最大5リクエストを並列に実行するような感じにしています。

検索毎のコストの試算

検索1回あたりのコストを試算してみます。(記事毎のベクトルを初回に生成するコストや、記事更新時の再生成のコストは省略します。)

  • 1記事あたり5,000トークンと仮定
  • ベクトル検索で上位50件を抽出して、Jev で評価する

記事の入力は、5,000トークン × 50件 = 250,000トークン となります。

Jev の公式料金は、入力100万トークンあたり0.042ドル(出力は無料)なので、計算すると 250,000 ÷ 1,000,000 × $0.042 = $0.0105 です。(検索文のベクトル化の費用はとても小さいのでここでは無視。)100回検索して $1.05 で済む計算になります。

これは、入力単価で比較すると、GPT-5.4 nano の約5分の1、GPT-5.4 mini の約18分の1に相当します。

🤶 10倍速いMovable Typeの再構築ツールをください 🧑‍🎄

  • 投稿日:
  • by
  • カテゴリ:

この記事は何か?

Movable Typeの再構築(静的なファイルの生成)の高速化に関する個人的な実験の記録です。

筆者の天野はMovable Typeの開発にエンジニアとして参加しており、開発元のシックス・アパートにも所属していますが、この記事で触れられているコードや結果については全て個人として実験した記録になります。

比較対象

今回はざっくりとした比較なので、ウェブブラウザのインターフェイスから再構築操作をした結果の処理時間をベースとします。「処理時間: 33秒」となっているので、ここからざっくり10倍速くできるかどうかを試します。

対象のデータは以下のスクリプトで投入しています。

  • 1500文字程度の内容で365記事
  • 12カテゴリ

コンテンツタイプの方が最適化のしがいがありそうですが、手を広げると実装が大変なので最初のベンチマークとしてはこんなところかなと思います。

docker composeで起動した環境上での比較なので、DB(mysql)も全て同じ端末上で動作している状態です。

アプローチ

以下のアプローチで高速化を試みます。3点目についてはややチートっぽさがありますが、「互換性を切り捨てれば高速化できる」という面は往々にしてあるので、こういった試みの中では妥当な選択肢であると思います。

  • Rustで実装する
  • 複数スレッドで処理する
  • コストのかかる互換性は維持しない

Rustで実装する

実行速度を優先すると2025年ではRustかGoあたりが有力な選択肢と思われますが、現状だと速度面やAIによる生成の効率など、公開されている調査結果を見るとRustが若干リードしているようなので、Rustを選びました。

またRustの方がWebAssemblyとの親和性が高く、ウェブブラウザ上やクラウドのエッジ上でも利用できる可能性があるというのも、特にMTとの関連では強みとなりそうです。

ウェブブラウザやエッジのランタイムだとTypeScriptという選択肢もあったりもします。MTのような使い方を踏まえるとTypeScriptの動的に拡張しやすいという点もとても魅力的なのですが、文字列操作が主な内容だと最速を目指すのは難しいと予想されるため、今回は選択肢に入れませんでした。

rust-mtml-parser

実は Rust にした理由はもう一つありまして、MTMLのパースは以前(今見たら3年前でした)書いた GitHub - usualoma/rust-mtml-parser というものを作っていて、これを使うと MTMLからASTを生成できるので、これを使いたいというのがありました。

上でも触れましたが、RustからはWebAssemblyを経由してnpmに変換する手段が用意されており、このパッケージもnpmのパッケージとして利用できるようにしています。以下は、npmパッケージを使ったパースと、パース結果のシリアライズ(整形)の実行例です。

rust-mtml-builder

今回実装したのは、そのパーサーを使ってパースした結果から、MTタグを評価して静的ファイルに書き出す部分です。以下がリポジトリです。(一部の実装しか含まれていませんが、理由は後ほど説明します)

GitHub - usualoma/rust-mtml-builder

実装のポイント

作業のほとんどはそれぞれのMTタグを実装していくだけなので、そこに関しては特にポイントはありません。実装が進んで細かいチューニングの段階に入れば、書き込み時の文字列処理で低レベルな最適化が入ってくると思いますが、今回はそこまではやっていません。

再構築の(もしくはSSGのような機能一般での)処理のボトルネックは多くの場合「SQLの問い合わせ回数」と「ファイルの読み書きのIO待ち」なので、それらの点の解消が性能を向上させるためのポイントになります。

結果

今回作成したのはコマンドラインのツールなので、ウェブブウラウザからの実行と比較するのはフェアではないのですが、まあざっくりなのでよしとして、以下のような結果になりました。

2秒以内で完了するようになりました。realと比べてuserやsysが大きいところから、マルチスレッドが使えていることが分かります。(この環境では10コアを利用しています)

なんちゃっての実装もあるので、作り込めば処理が複雑化して重くなる部分もあると思われるものの、一方で最適化も行っていないので、(多数のコアが使える環境であれば)10倍程度の高速化は十分に達成できそうだなという感触を得ることができました。

ちなみに並列化しないと(1コアのみを利用)以下のような結果ですが、それでも5倍程度は達成できそうです。

なぜ一部の実装しか含まれていないか?

リポジトリにAGENT.mdが含まれていることが分かるように、例によって実装には生成AIを利用しています。

今回やってみて、(使ったのは GPT-5.2ですが、おそらくそれに限らず多くの)AIはMTタグの挙動をすでに良く知っているので互換性のある実装をさらっと進めることができ、特に面倒なMTタグの実装の部分では大きな力を発揮できることが分かりました。

しかしここで、MTの本体はかつてはGPL(MITやBSDではない)で公開されていた時代もあるものの、現在はソースは公開されているもののオープンソースのライセンスではないという状況があります。

AIによって生成されたコードが内容的に似ているかというと、似てはいないように見えました。MTタグの仕様は定義されているので、単純にそれを満たすように実装してもそのようなコードになると思われる内容ではあります。しかしそのようなものであったとしてもなお、「こんな内容がさっとできてこれを公開してもいいのか(あるいは、それがGPL時代のコードから学ばれたものだと仮定しても、それを翻案したものがMITなどで公開されてもいいのか)」という点から見て、公開して差し支えのないものという確信が持てなかったため、タグの実装の公開は控えることにしました。

自分が書ける以上の内容のコードが生成されること、内容を理解していないコードを公開すること、など、来年以降ますますそのような状況に直面することが多くなると思われますが、あらためて、なかなか考えさせらる機会となりました。

この記事について

この記事はMovable Type Advent Calendar 2025の25日目の記事です!

今年も11月にはMTDDC Meetup TOKYO 2025に参加ささせていただいて、直接いろいろな人と話ができて楽しかったです。(MT9のフィードバックをいただけるのはこれからだということが分かった)

そしてアドベントカレンダーでいつも通りの年末感で締めくくることができました。今年もお世話になりました。

それではみなさん良いお年をお迎えください!

(この記事はMT9を使って書かれています)

トフはシックス・アパートの公式キャラクターです。CC BY-NC-SA 4.0 の下でライセンスされており、オリジナルは シックス・アパートのウェブサイトで入手可能です。

これは何か?

CMSの管理画面というのは基本的に無機質なものです。必須項目への入力を忘れれば「入力してください」と素っ気なく怒られ、頑張って記事を書いたとしても誰も褒めてくれません。SNSのメッセージ入力画面などはあれほど背中を押してくれるのに。

これはMovable Type 9に搭載された新しいリッチテキストエディタ(MTRichTextEditor)に、トフが編集を応援してくれる機能を追加して編集中の気持ちを少しでも上げようと試みるMovable Typeプラグインです。

GitHub - usualoma/mt-plugin-TophCheer
Contribute to usualoma/mt-plugin-TophCheer development by creating an account on GitHub.
github.com

追加される機能

  • ステータスバーの領域でトフが跳ねて応援してくれます
  • 行を追加するたびに文字数をカウントして応援してくれます
  • 内容を踏まえてOpenAIのAPIを使ってメッセージを考えて応援してくれます

追加されない機能

  • トフは応援するだけで、記事を書いてはくれません

設定

以下の項目を設定できます。

  • Secret Key
  • モデル
  • システムプロンプト

実装の概要

MTRichTextEditor がインストールされている環境だと、ステータスバーに表示するアイテムのための MTRichTextEditor.Component.StatusbarItemElement をグローバルで参照できます。これは HTMLElementを継承しいているので、これをさらに継承したクラスを定義してカスタム要素として定義することで、ステータスバーに表示するアイテムを追加できます。

AIエージェントに任せたプラグインの作り方

MTRichTextEditor 開発者向けガイドサンプルプラグイン があるので、それをエージェントに伝えてからコーディングを依頼すれば、それだけで割と動くものが出てきます。

この記事について

この記事はMovable Type Advent Calendar 2025の3日目の記事です!

トフについて

トフはシックス・アパートの公式キャラクターです。CC BY-NC-SA 4.0 の下でライセンスされており、オリジナルは シックス・アパートのウェブサイトで入手可能です。

これは何か?

カスタムブロックは非常に拡張性が高いのですが、「入力フォーム兼テンプレートとなるHTMLはブロックエディタで用意する」「挿入するカスタムスクリプトはHTML形式で、style要素やscript要素を混ぜたものになる」というところで、ローカル環境で開発がやややりにくいところがあったので、その問題を解決するべく開発ツールを作ってみました。

以前のものとの違い

カスタムブロックの開発ツールの用意は以前も試みたことがあり、そのときにはVSCodeの拡張機能として「MT Custom Block Editor」というものを作っていました。(記事はここです)これはVSCodeとの連携の試みとしては面白かったのですが、VSCodeに強く結びついてしまうため他のcliツールとの連携が逆に難しくなる面がありました。

そのような折、去年から今年にかけていくつか出したnpmをベースにしたツールが意外と興味をもってもらえたのもあり、こちらのアプローチに変えてみようと思い、以前のVSCode版とは違うものとして作りました。

こんな感じで動きます。

npm create @usualoma/mt-custom-block

このコマンドで雛形を作成できて、動画のサンプルのカスタムブロックも最初から入っています。

.jsonのファイル自体は、ゼロからつくるよりも一回MTで簡単な設定をしてから書き出すほうが楽だと思います。そこをベースにして、このツールでは主に以下のデータを更新できます。

  • アイコン
  • JavaScript または TypeScript
    • .tsというファイル名で保存すると勝手にトランスパイルします
    • script[type=module]で挿入されるので、DOMContentLoadedを明示的に書く必要はありません
  • 入力フォーム兼テンプレートとなるHTML

上の2つはエディタから、一番下はブラウザから更新します。(ブラウザの保存ボタンでちゃんとファイルも更新されます)

デモ動画をみてもらうとなんとなく分かりますが、エディタでTypeScriptを変更して保存するとブラウザの方も自動でリロードされ、TypeScript(カスタムスクリプト)の実行結果をすぐに確認できます。カスタムブロックの作成で一番手間がかかるのがここだと思うので、ここですぐに反映されるのは嬉しいのではないでしょうか。

データとしては本来、カスタムスクリプトにはlink要素やstyle要素を書くこともできるのですが、そのあたりはJavaScriptで動的に対応できるので、このツールでは割り切ってJavaScriptのみで動かすようにしています。

注意事項

ビルドのプロセスを経ることで、意図せずにJavaScriptを大きくなってしまいがちである点には注意が必要です。大きくなるとCMSのシステム側への負荷も大きくなるのでご注意ください。script[type=module]で読み込まれるので、ブラウザ上でのimportも普通に使えるのでnpmのモジュールの読み込みはcdnからの直接の読み込みなどを利用するのがおすすめです。

この記事について

この記事はMovable Type Advent Calendar 2024の25日目の記事です!

今年はMTDDC Meetup TOKYO 2024にも参加ささせていただいて直接いろいろな人と話ができ、そしてこのアドベントカレンダーでいつも通りの年末感も味わうことができました。どちらも主催者であった西山さんをはじめ、関係者のみなさん、ありがとうございました。おつかれさました。

それではみなさん良いお年を!

これは何か?

Movable Typeの管理画面のfaviconを自分の好きな画像に差し替えるプラグインです。画像はウェブブラウザに保存されます。

mt-plugin-ShortcutIcon

対応ウェブブラウザ

  • Google Chrome : 任意の画像を設定できます
  • Firefox : SVGやPNGは設定できますが、JPEGは設定できません
  • Safari : 設定されません

開発の動機

@usualoma/mt-plugin-builderを使って何ができるかをもう少し考えたくて、「ウェブブラウザだけでできること」というテーマで作りました。

仕組み

ユーザーが指定した画像をIndexedDBに保存して、ページ表示の切り替わりごとに読み出してURL.createObjectURLを使ってURLを生成して、link[rel="shortcut icon"] を作成してheadに挿入するという動作をしています。

誰のためのものか?

faviconを設定するだけなら画像のリソースもプラグインに含んでしまえばよく、そうすればユーザー毎に設定せずともインストールしたMT全体で差し替えることができます。

しかしここで、「制作会社のユーザー」と「クライアントのユーザー」で考えると、クライアントのユーザーは複数のサイトのMTを使うわけではないのでMTのデフォルトでよく(むしろそれが自然)、制作会社のユーザーは複数のサイトのMTを使うのでfaviconで「どのサイトのMTか?」を区別できるとよかったりすると思います。そのようなわけで意外と「ユーザーが自分の好きなfaviconを設定できる」という機能にも需要があるのではないかと思っています。

この記事について

この記事はMovable Type Advent Calendar 2024の12日目の記事です!