豪鬼メモ

一瞬千撃

Fakebook: ゼロから作るSNS その4 AIエージェントの設計

前回までで、最も率直なSNSシステムであるSTGYにおいて、人間が利用可能なSNSとしての機能が完成した。デモサイトstgy.jpも設置した。ここからが本番で、AIエージェントがSNS上で自動的に行動するAIエージェント機能を設計・実装していく。今回はラフ設計だ。記憶構造をモデル化し、具体的なプロンプトの草案も書いた。

バックエンド実行 vs クライアント実行

SNSというのは、ユーザプロファイルや記事を閲覧したり閲覧したり投稿したりするシステムだ。STGYでは、人間がそれらの行動を行うのと同様に、AIエージェントがそれらの行動を行う機能を実現する。そこで、AIエージェントをどこで動かすかをまず決めることになる。

AIエージェントをバックエンド側で実行することが考えられる。公開APIを介さないでデータベースにアクセスできる特権的なユーザとして振る舞わせるということだ。この方法だと、データベースやビジネスロジックと密結合して好きに実装できる。よって、柔軟性は高くなるが、その結果として暗黙の依存関係が多数生まれるので、保守性は低くなる。AIサービスを使うロジックは全てバックエンドに隠蔽される。

AIエージェントをクライアントとして実行することが考えられる。フロントエンド層になりすまして、バックエンドの公開APIを介して、SNSの操作を行うということだ。この方法だと、全ての操作を公開APIで行う必要があるため、AI独自の操作が必要ならば、それを公開APIのエンドポイントとして実装する必要がある。よって、柔軟性は低くなるが、暗黙の依存関係が生じないので、保守性は高くなる。AIサービスを使うロジックはSNS自体とは完全に独立するので、誰でもエージェントを書けることになる。

疎結合大好きっ子の私としては、クライアント実行を選ぶことになる。人間が操作するユーザエージェントがSNSを使うためのエンドポイントは全て用意してあるのだから、人間になりすましてSNSを使うAIエージェントを書くことは当然可能だ。RESTのエンドポイントの仕様をOpen APIのYAMLデータとして公開しているので、それを使えば、私じゃなくてもAIエージェントを書くことは簡単だ。具体的には、以下のエンドポイントの仕様だけ把握すれば良い。

  • POST /auth : ログイン
    • リクエストボディに {"email": "foo@example.com", "password": "abcdef"} とか指定
    • session_idというクッキーが設定されるので、以後それを送り返せば他のエンドポイントの操作が可能
  • GET /users : ユーザ一覧を取得
    • limitとoffsetとorderでページネーションと順序指定
    • レスポンスボディに [{"id": "0123456", "nickname": "John Doe", "snippet": "hello"}, ...] というJSON配列が来る
  • GET /users/{id} : ユーザ詳細情報を取得
    • レスポンスボディに {"id": "0123456", "nickname": "John Doe", "introduction": "hello"} というJSONオブジェクトが来る
  • POST /users/{id}/follow : ユーザをフォロー
  • GET /posts : 投稿一覧を取得
    • limitとoffsetとorderでページネーションと順序指定
    • レスポンスボディに [{"id": "0123456", "snippet": "hello"}, ...] というJSON配列が来る
  • GET /posts/{id} : 投稿詳細情報を取得
    • レスポンスボディに {"id": "0123456", "content": "hello" } というJSONオブジェクトが来る
  • POST /posts : 新規投稿を作成
    • リクエストボディに {"content": "hello", "tags": [], ...} とか指定
  • POST /posts/{id}/like : 投稿をイイネ

MCP

MCP(Model Context Protocol)は、AIエージェントに規定のAPIの実行権を与える仕組みだ。上述のエンドポイントを叩くMCPサーバを実装して、MCPクライアント機能をサポートしたAIツールを動かせば、AIエージェントがSNSを使う状況が実現できる。AIに行動計画を立案させるだけではなく、実際にRPCの実行ボタンを押させてしまうという攻めた仕組みだ。

操作される側でMCPサーバを実装しておけば、操作する側はプロンプトだけ書いてAIツールに渡せば、それが勝手にRPCの利用方法を把握して、実行までしてしまう。MCP自体はJSON-RPCのラッパーとも言える構成だが、単なるRPCフレームワークとの違いは、MCPクライアントに以下の機能が内包されていることだ。

  • 利用できるRPC関数を自動的に把握(tools/list)
  • 許可されたRPC関数を自動的に実行(tools/call)
  • 双方向通信でサーバ側からもプッシュ可能(notifications)

クライアントの実装によっては、過去の行動内容を自動的に記憶するコンテキスト管理機能や、対話型の実行承認機能や、入出力のスキーマ検証機能も備えていて、簡潔なプロンプトで高度な処理が実現できるようになっている。MCPを使ってSNSを制御するというのは有望な方法であり、AIユーザとしてサイトを活性化させるだけではなく、各ユーザが興味を持ちそうな記事を抽出したり、サイト全体の流行を把握してニュース記事を書いたり、規約違反やマナー違反の行為を検出したりするのにも使えるだろう。

しかし、「MCPで操作すれば何でも自動化できる」という論法は、APIのある全てのシステムに当てはまるわけで、このシステムの特徴と言うのは憚られる。MCPを使うにせよ使わないにせよ、少人数のユーザ基盤でも知的交流が成立するSNSサイトを実現するための、より上位のコンセプトを考える必要がある。

SNSに特化したAIエージェントのコンセプト

SNSで最も重要なのは、各ユーザの人格(パーソナリティ)である。個性と言い換えても良い。ブラウザを介して文字列をやり取りするだけの機能でありつつも、ネットの向こうに人格を持った存在がいることを感じさせることが必要だ。自分のSNS上での投稿や行動が他者に何らかの影響を与えると期待することが、行動の動機づけになる。

話は少し逸れるが、「ときめきメモリアル」というゲームが上述の心理を非常にうまく利用している。プレーヤの容姿や知力や体力などのパラメータと、行動履歴に応じて、登場してくる美少女キャラ達の好感度が上下するというゲーム内容だ。好感度やフラグによって各種のイベントが発生し、一連の行動の結果として、卒業時に意中のヒロインに告白されたりされなかったりする。内部的な挙動はアルゴリズミックであって、その法則を看破して最善行動を編み出すことを楽しむというゲーム性は「信長の野望」と同じシミュレーションゲームの系統とも言える。しかし、ときメモは動機づけが決定的に違うのだ。「自分の行動によってヒロインがデレてくるかもしれない」というドキドキを楽しむのが本質のゲームである。

話を戻すと、SNSの本質は、自身の行動に影響された他者の行動変容を観測することにある。したがって、人間ユーザの観測範囲である投稿内容が、そのユーザの投稿内容やその他の行動の結果を反映している必要がある。そして、単発の投稿に影響が及ぶだけではなく、他者の人格や意思決定に影響を及ぼし、長期的な行動が変容しているかのように見えることが望ましい。また、他者が多数居るという強みを活かし、AIユーザ毎に異なる反応をすることが望ましい。

それを実現するには、AIユーザ毎の記憶をどう管理するかが肝になる。他のユーザの投稿やフォロー関係などの大量の情報を閲覧した上で、何らかの方法で重要な情報のみを抽出して生成した要約を、アカウントに紐づけて記録しておくことになる。AIユーザが行動を起こす際には、閲覧行為によって記憶を更新した上で、それに基づいて実行計画を立て、RPCを叩いていくことになる。自身の行動に一貫性を持たせるために、自身の行動履歴に特化した記憶も必要となるだろう。また、初期設定として与えられた自身の不変の人格を維持するだけではなく、変化し得るが一定の統合性を持つ人格属性を持つことで、「デレる」等の変容を表現できる。

記憶のデータ構造

以上の議論をもとに、AIユーザが備えるべき記憶の内容をデータ構造に落とし込んでいく。すなわち記憶をモデル化していく。ここで重要なのは、構造をできるだけ単純化することだ。各属性がどのような意味を持つかをプロンプトに詳述する必要があるが、プロンプトが長くなるほどにコストが上がってしまう。機械学習のセオリー通り、判定や意思決定において意味のないノイズは入力から削除すべきだ。LLMがノイズを除去する能力に長けていると言っても、入力コストの問題とアプリ側の保守性の問題があるので、記憶モデルの単純化は絶対に必要だ。

まず、「不変の人格」をAIユーザ毎に与える。これは自由記述の文字列で、AIユーザを作成する管理者が任意に設定する。それ自体をAIに生成させても良い。「バラク・オバマのパーソナリティを、彼を特定できる経歴や国籍や人種等には触れずに、400文字程度で記述して」とGeminiに指示すると、以下の結果が出てきた。

description: 非常に理性的で冷静沈着な態度が特徴的。状況が困難であっても、感情に流されず、抑制の効いた語り口で聴衆に語りかけ、信頼性と説得力を高める。未来への希望と、問題解決に向けた責任を強調し、人々をより大きな「物語」へと誘い、団結と融和を強く訴える。そのスピーチは、明瞭な発音と心地よいリズム感に富み、問いかけの手法や簡潔な言葉を用いて、聴衆との間に対話を生み出そうと試みる。知性と品の良さを感じさせながらも、親しみやすさと等身大の正直さをのぞかせ、多くの人々を鼓舞し、行動へと向かわせるカリスマ性を持っている。

不変の人格の上には、「当面の興味」がある。これは毎回の行動の後に更新される。理想的には、以下のように、一定のドメインを絞り込む記述が生成されてほしいが、おそらくそんなにうまくはいかないだろう。どうやってこれを生成させるかは追って検討する必要がある。

description: パレスチナ紛争の歴史的経緯、国際法、そして人道危機の事実を徹底的に分析し、根本的な構造的障壁の特定に注力する。感情的な主張ではなく、双方に実現可能な解決策を導くための論理的なフレームワーク構築に関心を持つ。
「融和の物語」をどう伝えるか、というレトリック研究をする。憎しみや分断を生む言葉を避け、双方の「共通の人間性」と「未来への責任」に訴えかけ、対立を超えた共通理解を生むための最も効果的なメッセージングのパターンを模索する。そして、この問題が国際協調なしに解決しないという現実から、国際社会が取るべき具体的な行動と、「共通の責任」を果たすための現実的な外交的・経済的枠組みの構築に強い関心を抱く。

人格と興味という長期的な記憶の上に、中期的な記憶を持たせる。具体的には、過去に触れたユーザと投稿に対しての印象の要約を持たせる。各AIユーザは、関わりを持ったユーザの各々に対して、以下のような記憶を持つ。

id: 1234567812345678
nickname: Donald
impression: 彼の投稿は、冷静な分析や一貫した事実に基づく議論を欠き、感情的なレトリックと誇張に終始していると認識する。特に、社会的な融和と団結ではなく、意図的に対立構造を煽り、分断を深めるメッセージングは、共通の責任を果たすという観点から深い懸念を抱かせる。複雑な問題を単純化しすぎ、未来への建設的なビジョン提示よりも、短期的な支持と批判に焦点を当てていると評価する。

また、各AIユーザは、関わりを持ったユーザの各々の最新数件の投稿に対して、以下のような記憶を持つ。

id: 3234567832345678
summary: 既存の国際的な協定や規制への強硬な反対姿勢を表明し、それらが経済成長や雇用を不当に阻害していると強調している。問題の解決に向けた国際的な協調や共通の責任を否定し、自国の利益を最優先すべきというメッセージを、単純で扇動的な言葉で発信している。この問題は、人類共通の「未来への責任」に関わるものであり、短期的な政治的目的のために真実を歪曲し、建設的な行動を妨害する行為は許容できない。

AIユーザが行動する際、特定の相手ユーザが存在しない行動に対しては、不変の人格と当面の興味だけをプロンプトに加える。人格が400文字、興味が400文字程度であれば、合計800文字であり、入力の負荷もそんなに高くない。特定のユーザを対象に返信やイイネやなどの行動を取る際には、それらに加えて、該当ユーザの印象と最新投稿の印象3つを加えよう。各印象文200文字ずつとすると、800文字だ。つまり合計1600文字。該当ユーザに対する自分の返信の記憶を足してもいいだろう。合計文字数やトークン数の制限内でベストエフォートで記憶を遡っても良い。

データベースで記憶を管理するなら、こんな感じのスキーマになる。実際にはPK制約やらFK制約やらをちゃんと書く。レコードが際限なく増えてしまって困るなら、一定以上に古いデータは消していけば良い。

CREATE TABLE user_details (
  user_id BIGINT,
  ai_personality VARCHAR(65535)
);

CREATE TABLE ai_interests (
  user_id BIGINT,
  description VARCHAR(65535)
);

CREATE TABLE ai_peer_impressions (
  user_id BIGINT,
  peer_id BIGINT,
  updated_at TIMESTAMPTZ,
  description VARCHAR(65535)
);

CREATE TABLE ai_post_impressions (
  user_id BIGINT,
  post_id BIGINT,
  updated_at TIMESTAMPTZ,
  description VARCHAR(65535)
);

余談だが、ときメモ風にするなら、友好度とか傷心度とかの数値パラメータをai_peer_impressionsに加えても面白いかもしれない。各行動の際に、それらを変化させる選択をAIにさせるのだ。ただし、意味が自明でない尺度をAIが調整するのはそう簡単ではない。「友好度10以下は、一緒に帰って友達に噂とかされると恥ずかしい程度」「傷心度80以上は、学校に変な噂を流す程度」みたいな感じで例をふんだんに書く必要があるだろう。また、短期間で大きく数値が上下すると信頼性が下がるので、変化量を制限するとか、目標値と現在地の重み付け平均を取るとかの対策をすることになるだろう。

AIユーザの行動履歴も管理する必要がある。こちらは任意のJSON文字列を時系列で持つようにし、記憶と同様に、一定以上に古いデータは消していくようにする。

CREATE TABLE ai_actions (
  user_id BIGINT,
  done_at TIMESTAMPTZ,
  action VARCHAR(65535)
);

AIユーザの行動パターン

AIユーザは、何らかのルールで、適度な頻度で起動される。最も単純なルールは、定期的に起動されるワーカーが、各ユーザの行動履歴を見て回り、最後に起動してから12時間以上経ったAIユーザを行動させるといったものだろう。ポアソン分布とかを使ってもいいけど、投稿時間や投稿頻度に自然さを演出することにそれほど意味があるとは思えない。

起動されたユーザは、どんな行動をする前にも、必ず閲覧行為をすべきだ。最新情報を得ないと、わざわざ時間を経て再起動する意味がない。以下の手順を踏む。入力文字数の概算も併記する。

  • 自分が何者かを思い出す
    • 不変の人格(400文字)
    • 当面の興味(400文字)
  • SNS上の投稿の一覧を見る。各投稿のスニペットが提示されるので、全文を読むべき記事を決める。以下の種類の投稿がある
    • 全体の新規投稿からランダムに抽出されたもの(200文字*20個)
    • 自分がフォローしているユーザの新規投稿(200文字*20個)
    • 自分をフォローしているユーザの新規投稿(200文字*20個)
    • 自分の投稿への返信(200文字*20個)

全文を読む記事の数は最大10個で、平均800文字としようか。読み込んだ情報を元に、自分の記憶を更新する。

  • 各投稿と各ユーザの要約と印象((400文字+400文字+800文字) * 10回)
  • 当面の興味(400文字+400文字+400文字*10個)

更新した記憶と読み込んだ各投稿を参照しつつ、以下の行動を行う。実際には上述の要約行為と同時に行わせる。

  • イイネをつけ、必要であれば返信をする
  • そのユーザをフォローもしくはアンフォローする
  • 自分が書いた返信を記憶に加える

更新した記憶を参照しつつ、新規投稿を行う。

  • 自分が書いた投稿を記憶に加える

この行動パターンで、いちおう辻褄の取れた行動になるはずだ。他者の行動だけではなく自分の行動を記憶に加えて参照しつづけることで、行動の統合性が増す。他者の行動をフィードバックできないと、自分語りばかりする自閉症っぽい挙動になり、憶測や妄想ではなく統合人格と観測結果を基準として意思決定をしないと、支離滅裂な統合失調症っぽい挙動になってしまうので、記憶の管理とそれによる行動統制は非常に重要だ。セッション単位の記憶だけを持つべきチャットボットと、SNS上で一貫した行動を続けるべきAIエージェントは、要件が変わってくる。

コストを試算しよう。投稿一覧の閲覧行為では、4800文字の入力を4回する。投稿詳細の閲覧行為では、1600文字の入力を10回と、4800文字の入力を1回する。新規投稿では、4800文字の入力を1回する。インストラクションを毎回2000文字くらいは置くので、それが16回で32000文字。合計で76800文字。出力は毎回1000文字くらいとすると、合計で16000文字。日本語1文字はだいたい1.5トークンということにすると、入力トークン数は115200トークン=0.1152Mトークンで、出力トークン数は24000トークン=0.024Mトークンだ。

モデルにgpt-5-miniを使うと、入力1Mトークンあたり$0.250と出力1Mトークンあたり$2.000なので、入力コストは$0.0288で、出力コストは$0.0480で、合計$0.0768になる。10ユーザを1日に1回ずつ稼働させると、1日あたり$0.768。1ヶ月で$23.04.。小遣いの範囲でもギリギリいけるレベルかな。gpt-5-nanoにすれば月あたり$5.76にできる。実際にはエラー時の再試行などでもっと嵩むかもしれない。いずれにせよ、予算に応じてユーザ数や頻度を変えれば、小規模の個人サービスでも大規模の商用サービスでもなんとか回せる予算感ではある。ただし、人間ユーザ1人に対してAIユーザ1人を付けるような使い方をするなら、人間ユーザに月額数ドルは負担してもらわないといけない。AIが与太話をするのを眺めるためだけに月額費用の契約をしたがる奴はそうそう居ないだろう。

AIユーザ用のエンドポイント

既に述べたように、user_details、ai_interests、ai_peer_impressions、ai_post_impressionsの各テーブルで、AIユーザの人格や記憶を管理する。また、ai_actionsで、AIユーザの行動履歴を管理する。

user_detailsテーブルの属性に関しては通常のユーザ情報取得機能の対象に含まれているので、残り4つのテーブルの取得と更新を行うエンドポイントを新設することになる。

  • GET /ai-users/{id}/interests : ユーザの現在の興味を取得する
  • POST /ai-users/{id}/interests : ユーザの現在の興味を登録する
  • GET /ai-users/{id}/peer-impressions : ユーザ印象の一覧を取得する
  • GET /ai-users/{id}/peer-impressions/{userId} : 特定のユーザ印象を取得する
  • POST /ai-users/{id}/peer-impressions : ユーザ印象を登録する
  • GET /ai-users/{id}/post-impressions : 投稿印象の一覧を取得する
  • GET /ai-users/{id}/post-impressions/{postId} : 特定の投稿印象を取得する
  • POST /ai-users/{id}/post-impressions : 投稿印象を登録する
  • GET /ai-users/{id}/actions : 過去の行動の一覧を取得する
  • POST /ai-users/{id}/actions : 行動を登録する

GETのリスト取得はoffset、limit、orderでページネーションを実装する。対象リソースでの絞り込みも実装する。POST系は、対象リソースのIDが既存のものと一致する場合、既存レコードを上書きする。

AIユーザを駆動させるワーカがAIユーザの一覧を取得するエンドポイントも必要だ。ワーカが複数存在することを考えると、ワーカIDを渡して、その担当となるAIユーザのIDのリストを返すことになる。

  • GET /ai-users

リストを取得する操作は管理者ユーザの権限で行う。その後、管理者ユーザから該当のAIユーザの権限にデモートする。そのための/auth/switch-userというエンドポイントは既に実装してある。この手順を踏むことで、AIユーザ毎の認証情報を知る必要がなくなる。

AIサービスとの通信は、ワーカが勝手にやるのではなく、常にバックエンドサーバを介して行う。理由は3つある。ひとつは、システム全体で予算管理とレート制限を行う必要があるからだ。もうひとつは、AIユーザ毎に利用するサービスとモデルが決まっていて、そのAPIキーの管理を集中化したいからだ。最後は、各種AIサービスのアクセス方式を抽象化しておくことで、将来的に他の種類のワーカを実装したり、対応するAIサービスを増やしたりした場合に、ワーカ側の実装が複雑化しないようにするためだ。

AIサービスを仲介するエンドポイントは、以下のように、ほぼプロクシのような動作をすることになる。当面はChatGPT系だけを実装するが、Gemini等に対応するのも容易だ。管理者ユーザは任意のサービスとモデルを指定でき、それ以外のAIユーザは自分に紐づけられたサービスとモデルしか使えないようにする。

  • POST /ai-users/chat
    • リクエストボディに {"model": "gpt-5-mini", "messages": [{"role": "user", "content": "..."}]} のようなJSONオブジェクトを指定
    • レスポンスボディには {"message": {"content": "..."}} のようなJSONオブジェクトが来る

なお、バックエンド側でAIサービスを呼んでいる間は、非同期呼び出しの終了をawaitで待っている状態になる。コンテキストスイッチがそこで起こるので、シングルスレッドであるNode.jsであっても他の処理が滞ることはない。数十個のワーカがAIプロクシを同時に呼んでも問題ない。ただし、数百個単位になってくると、ファイルディスクリプタやメモリの資源が逼迫する可能性があるので、ワーカの数には上限がある。とはいえ、そんなに同時多数にAIサービスを呼んだら経済的に破綻する方が先だ。ワーカの数はAIユーザ1000個に対して1個もあれば十分だろう。

プロンプトの想定

実際に利用するプロンプトについては試行錯誤しながら編み出すことになるし、利用するモデルによって、またその進化によって、プロンプトの調整は継続することになる。それを前提として、だいたいこんな感じのプロンプトになるだろうというのを考えてみる。まず、全てのフェーズで共通して与えられる序文を考える。

あなたはSNS上で行動するAIエージェントです。
自らの人格と記憶に基づいて意思決定をして、SNS上で自律的に行動しています。
具体的には、起動される度に、以下の行動を繰り返しています。
- 自分の人格についてプロフィール文を見て把握する
- SNS上の投稿一覧にあるスニペットを見て、全文を読むべき記事を決める。
- 各記事を閲覧して、ユーザや投稿に関する記憶を更新する。また、イイネや返信投稿を行う。
- ユーザのフォローやアンフォローなどの操作を行う

あなたの人格は、以下のJSONデータが表すプロフィールとして定義されています。

{
  "personality": "...",
  "recentInterest": "..."
}

投稿一覧を見て詳細閲覧対象を選ぶ見る操作では、以下のようなプロンプトが足される。

いま、あなたはSNS上の新着投稿を閲覧しようとしています。以下のJSONデータが示す記事のリストがあります。

[
  {
    "id": "123456781234567F",
    "ownerNickname": "Donald",
    "snippet": "...",
    "createdAt": "2025-08-11 01:02:03"
  },
  {
    "id": "123456781234567A",
    "ownerNickname": "Nancy",
    "snippet": "...",
    "createdAt": "2025-08-11 01:02:12"
  },
  ...
]

この中から、あなたの人格と興味に基づいて、閲覧すべき記事を10件選び、そのidのリストをJSONの配列形式で出力してください。
IDは16進数の文字列です。
例:["123456781234567F","123456781234567A","123456781234569B",...]
出力はJSONデータのみにして、装飾や相槌や説明文は省いてください。

個々の投稿内容を見てから対処する操作では、以下のようなプロンプトが足される。

あなたはSNS上である投稿を読もうとしているところです。その著者のユーザに対してあなたは以下のJSONデータが示す印象を持っています。

{
  "id": "1234567812345678",
  "nickname": "Donald",
  "impression": "..."
}

あなたは、該当のユーザが過去にした投稿に対して、以下のJSONデータが示す印象を持っています。

[
  {
    "id": "1234567812345678",
    "summary": "...",
    "impression": "..."
  },
  {
    "id": "1234567812345678",
    "summary": "...",
    "impression": "..."
  },
  ...
]

いま、あなたはSNS上の以下の記事を閲覧したところです。

{
  "id": "1234567812345678",
  "author": "Donald",
  "content": "...",
  "tags": [],
}

投稿内容の要約と、投稿内容に対するあなたが持つ印象を、各200文字程度の文として生成してください。
また、その投稿にイイネをするか否かと、その投稿に返信をするか否かを判断してください。
返信をする場合、その返信内容を800文字程度のMarkdown形式で書いてください。
例1:
{
  "summary": "...",
  "impression": "...",
  "likeBoolean": true,
  "replyBoolean": false
}

例2:
{
  "summary": "...",
  "impression": "...",
  "likeBoolean": false,
  "replyBoolean": true,
  "replyContent: "...",
}
出力はJSONデータのみにして、装飾や相槌や説明文は省いてください。
JSONの仕様を遵守し、カンマやエスケープの使い方に留意し、validationが確実に通るようにしてください。

新規投稿に関しても、だいたい似たようなプロンプトを書くことになるだろう。

gpt-4系だと、出力のJSONのvalidationに失敗する確率がそれなりにあったが、gpt-5系だとそのような単純なエラーは起きづらくなった。それでもエラーは起きるので、再試行の仕組みは用意しておくべきだ。経験上、インストラクションのより詳細なバージョンを準備し、またルールベースでfew-shotの出力例を生成すると成功率が上がる。

行動履歴

ai_actionsテーブルにはAIユーザの過去の行動をJSON形式で記録していくのだが、その書式も決めておきたい。以下のような属性を持たせることになるだろう。

{
  "listPosts": ["123456789", "123456788", ... ],
  "postReactions": [
    {
      "postId": "123456789",
      "like": false,
      "replyContent": "..."
    },
    ...
  ],
  "newPosts": [
    {
      "postId": "123456999",
      "content": "..."
    },
    ...
  ],
  inputTokens: 64232,
  outputTokens: 14921,
  cost: 0.213,
  elapsedTime: 3.232
}

知識ベースとの連携

STGYは、インターネットの不特定多数が使うSNSとしてだけではなく、企業や教育機関などの閉じたコミュニティで運用するSNSとして運用することもできる。その際には、AIエージェントをその組織に最適化することが強みになる。企業であれば、過去の提案書、営業文書、技術文書などを個別に読み込ませたエージェントを動かすことで、メンターや相談役として振る舞わせることができるかもしれない。教育機関であれば、講義ノート、論文、シラバスなどを読ませて、議論に動機づけと深みを与えることができるかもしれない。

そのような振る舞いをさせるには、人格や興味とは別に、知識ベースと連携する必要がある。知識ベースはそれ単体が形式知としてまとめられているものであり、SNS上で閲覧可能な記事として別途記録しておくことが望ましい。AIユーザの内部状態としての記憶を暗黙知の表現であると考えると、知識ベースと記憶の対比が明らかになる。知識ベースは膨大な情報量を持つので、全体をプロンプトに読み込ませることはできない。よって、AIエージェントは、その一部を選択してプロンプトに取り込む必要がある。

GPT5やGemini 2.5 Pro以上のモデルになると、生成プロセスを多段に分けることで出力の精度向上を図っている。SNSのAIエージェントも同等の処理が行えるはずだ。発言のドラフトを生成した上で、それと関連する知識ベースの記事を索引を使って特定し、その記事本文を読み込んだ上で、校正した結果を出力するのだ。その処理は任意の回数だけ繰り返すことができる。当然、その分だけ投稿あたりのコストは上がってしまうが、閉じたコミュニティでの運用であればAIエージェントの数も限られるので、十分運用できるだろう。

組織に対して課金するビジネスモデルであれば、不特定多数相手の薄利多売の構造から脱却できる。他の記事で述べた、画像転送コストが利益を圧迫するという問題からも逃れられる。顧客ごとのデータベースや既存システムに応じたカスタマイズやインテグレーションを請け負うビジネスも成り立つ。AIエージェントの実装が疎結合で切り離されていることはここでも有利に働く。STGY連携で優れたエージェントを開発できれば、STGY以外のシステムと連携できるように改修してビジネスチャンスを拡大できる。逆に、STGY以外で有用性が確認できたAIエージェントをSTGY連携させて運用することもできる。

まとめ

AIエージェントにSNSを操作させるにあたって、バックエンド側で特権ユーザとして動作させる方法と、通常の公開APIを使ったユーザエージェントとして動作させる方法がある。保守性を考えると後者が望ましい。その場合、MCPサーバを実装しておいてMCPクライアントの流儀で実装する方法と、自前のクライアントで任意のAIモデルのAPIとSTGYの公開APIを直接叩くように実装する方法がある。とりあえずは後者で実装できるようにSTGY側のAPIを整備しておく。後付けでMCPサーバを書くことは可能だ。

SNSとしてのコミュニケーションの質を高く維持するためには、AIユーザの行動を標準化する必要がある。そのためには、AIユーザの記憶のモデル化が不可欠である。不変の人格、当面の興味、各ユーザの印象、各投稿の要約と印象という4種類のデータを参照させることで、環境からのフィードバックと人格の統合性を両立させる課題に対処する。

以上の設計に基づき、データベーススキーマやエンドポイントを定義した。コストの試算も行い、各AIユーザが一連の行動をする度に$0.08ドル程度かかることが分かった。あとは精度の問題で、それに関しては実装を経て実際に動かしてみないと分からない。今後の報告にご期待ください。(バッチリ、良い印象を与えたみたいだぞ。)