🥭

Convex はもう「気持ちいい BaaS」だけではなかった

に公開

以前 Convex を少し触ったとき、自分の中ではかなりシンプルな印象がありました。

TypeScript で書きやすくて、リアルタイム更新も自然に入ってくる。個人開発で試すぶんには手触りがよくて、「開発体験のいい BaaS」だな、という理解でした。

https://zenn.dev/chot/articles/582c381a80d0d1

この見方は、今読み返してもそこまで外れていないと思います。実際、今の pricing を見ると、Free & Starter は個人開発やプロトタイプ用途を意識した構成ですし、EU hosting も全プランで利用できます。

ただ、最近あらためて docs を追ってみると、以前とは少し景色が変わって見えました。

今の Convex には、Components、Agents、MCP server、Agent Mode のといった要素があります。単に「LLM とつなげられる」という話ではなく、AI や agent を使うアプリをどう組み立てるか、その前提ごと整えてきているように感じています。

今回はあくまで個人開発の範囲ですが、最近の Convex を見て印象がどう変わったか、そして小さなデモアプリを作ってみて何が見えたかを書いてみます。

Convex は「気持ちいい BaaS」だった

前回 Convex を紹介したとき、自分が見ていたのはかなり素直な部分でした。TypeScript で完結しやすいこと、リアルタイム更新を扱いやすいこと、バックエンドもフロントエンドも JavaScript / TypeScript に寄せやすいこと。そのあたりがよくできていて、少なくとも小さく試すぶんには触りやすかったです。

なので、当時の自分にとっての Convex は、あくまで「使いやすい BaaS」でした。Supabase と比べると、書いていて気持ちいいな、くらいの感覚です。

最近の Convex は AI アプリの基盤を目指している?

最近の Convex を見ていて印象が変わったのは、AI 関連機能が増えたこと自体よりも、AI を使うアプリの形が最初から意識されている と感じたことでした。

そのきっかけになったのが Components です。Components は、コードだけではなくデータも含めて、閉じた単位として機能を持てる仕組みです。しかもアプリ本体とは分離されていて、勝手にアプリ側のテーブルを読んだり、関数を呼んだりはできません。状態を持つバックエンド機能を、ひとつのまとまりの単位として載せやすくなっています。

その上に Agents があります。Agents では thread や message が前提になっていて、会話履歴は保存され、続きの会話もそのまま扱えます。streaming も最初から組み込まれています。単発で AI を呼ぶというより、会話の流れを持つアプリを作るための部品が、最初からひと通り揃っている印象でした。

ここまで見てくると、最近の Convex は「リアルタイム BaaS」と説明するより、AI アプリを作るためのバックエンド基盤 として見たほうが、自分にはしっくりきました。

小さいデモアプリを作ってみた

ここまでの話だけだと少し抽象的なので、小さいデモアプリも作ってみました。

やったことは単純で、固定メモをひとつ選ぶと、その内容を前提に AI と短く会話できる、というものです。左にメモ一覧、右上に選択中のメモ本文、右下に会話履歴を置いています。やり取りはメモごとに残ります。

このアプリで見たかったのは、AI の回答精度そのものではなく、いま何について話しているのかが、画面の中で自然にひと続きに見えるか でした。

今回のデモでやったことはかなり絞っています。

  • 固定メモを数件だけ用意する
  • 1つのメモに対して 1 つの thread を持つ
  • 会話はその thread に紐づいて続いていく
  • AI には、メモ本文とその thread の履歴を前提として渡す

今回は、会話が続いていく最小単位のアプリ として Convex がどう見えるかを確かめたかったので、メモの作成や編集、RAG、複数スレッドの切り替えのようなものは入れていません。

概念としては「1メモ = 1thread」です。ただ、実装では app 側が持つデータをかなり薄くしています。notes にメモ本体を置いて、threads には noteIdagentThreadId の対応だけを持つ。実際の message は Agent component 側に寄せました。

この分け方にしたのは、会話の中心を自前テーブルではなく component 側に置いたほうが、Convex が今どこに重心を置いているのか見えやすい と考えたからです。

コードで見ると

アプリ側のスキーマはかなり薄いです。

export default defineSchema({
    notes: defineTable({
        title: v.string(),
        body: v.string(),
        position: v.number(),
    }).index("by_position", ["position"]),
    threads: defineTable({
        noteId: v.id("notes"),
        agentThreadId: v.optional(v.string()),
    }).index("by_noteId", ["noteId"]),
});

ここで持っているのは、メモ本体と、「このメモに対応する agent の thread はどれか」という対応関係だけです。message を app 側のテーブルに持っていない時点で、会話の重心がふつうの CRUD とは少し違う場所にあります。

表示側では listUIMessages で履歴を取ってきて、送信時にはまず ensureNoteThread でそのメモの thread を解決してから generateText を呼びます。

const paginated =
    thread?.agentThreadId !== undefined
        ? await listUIMessages(ctx, components.agent, {
              threadId: thread.agentThreadId,
              paginationOpts: { cursor: null, numItems: 64 },
          })
        : { page: [] };

const noteThread = await ctx.runMutation(internal.chatData.ensureNoteThread, {
    noteId: args.noteId,
});

const { thread } = await agent.continueThread(ctx, {
    threadId: noteThread.agentThreadId,
});

const result = await thread.generateText({
    prompt: args.text,
    system: buildNoteSystemPrompt({
        noteTitle: noteThread.note.title,
        noteBody: noteThread.note.body,
    }),
});

Agents の docs でも、messages は thread に紐づいて保存され、会話コンテキストは各 LLM call に含まれる前提で説明されています。

デモでは、「個人開発アイデア」というメモを選び、「筋トレの個人開発アイデアを2つ出して」と聞いたあと、「1についてもっと深掘りして」と続けています。

やっていること自体は地味です。ただ、ここで大事なのは賢い返答が返ることではなく、前の会話を踏まえた続きを、同じ場所に無理なく積めること でした。

ここまで作ると、Convex の見え方も少し変わってきます。
単に「LLM を呼べるバックエンド」ではなく、会話が続いていくことを前提にしたアプリを置きやすい場所 として見えてきます。

MCP server や Agent Mode, Workflows について

今回のデモで触れたのは、あくまで小さい会話アプリの部分だけです。ただ、公式 docs を見ていると、その先に何を見ているのかも少しずつわかってきます。

MCP server は、AI agent が Convex deployment に対して状態確認や関数実行などを行うための公式の入口です。production へのアクセスはデフォルトで無効になっていて、明示的に有効化しない限り触れません。安全面まで含めて、かなり実務を意識した作りです。

Agent Mode は、cloud 上の coding agent が自分の普段の dev deployment をそのまま触って衝突しないように、別の development backend を使いやすくする考え方になっています。いわゆる「アプリの中で AI を使う」だけでなく、「開発の中で AI がバックエンドを触る」ことまで視野に入っているのが、最近の Convex らしいところだと思います。ちなみに Agent Mode は beta です。

もうひとつ注目したのが、複雑な処理を安定して動かすための機能である Workflows です。

公式の説明を見ると、これらは「時間のかかる作業を、途中で失敗してもやり直したり、少し待ってから再開したりしながら、最後まで確実にやり遂げるための仕組み」として紹介されています。会話アプリをもう少し本格的に作り込んでいくと、「外部のサービスと連携する」「返事を待つ」「作業をいくつかに分ける」といった要素がどうしても出てきます。す考えると、Convex がこうした機能を整えているのは自然です。

おわりに

以前の自分は Convex を「TypeScript-native で、リアルタイム更新が気持ちいい BaaS」と見ていましたが、やはり最近はAI基盤に力を入れてるんだな〜と感じました。

今回触ったのはその入口にすぎません。それでも、小さいデモをひとつ作ってみただけで、「ただ LLM を呼べる」以上の方向を見ていることは伝わってきました。

次に触るなら、RAG や Workflows まで含めて、もう少し長い処理や会話の流れを持つ形で試してみたいです。

chot Inc. tech blog

Discussion