音威子府村のAI副村長「ねっぷちゃん」を支える技術 - 地域に根ざすAIのつくりかた -

こんにちは、技術部の大脇です。

カヤックでは2026年2月19日に北海道中川郡音威子府村(おといねっぷむら)にて、対話型AI副村長「ねっぷちゃん」をリリースしました。
本プロジェクトはオープンR&Dとしてリポジトリを公開しながら開発・運用しています。
www.kayac.com

今回はねっぷちゃんのアーキテクチャの抜粋と、その設計に至った背景をご紹介します。

主な技術スタック

ねっぷちゃんは主に以下のような技術スタックで構成されています。

  • インフラ
    • Cloudflare Workers / Pages / D1 / R2 / Vectorize / Queues
  • バックエンド
    • フレームワーク: Hono
    • AIエージェントフレームワーク: Mastra
    • LLM: Gemini(Google AI)
    • ORM: Drizzle ORM
    • バリデーション / スキーマ定義: Zod
  • フロントエンド
    • UIライブラリ: React
    • ビルドツール: Vite
    • データフェッチ: TanStack Query
    • APIクライアント: openapi-fetch
    • スタイリング: Tailwind CSS
  • Linter / Formatter: Biome
  • Test: Vitest

技術選定の指針について

企画立案の後、初期段階のねっぷちゃんを村へ持っていくまでの実装期間は約2週間という短期間だったため、スピード感を持って開発できる構成にする必要がありました。
一方で、今後の機能拡張やプラットフォーム追加を見据えると、初期段階の設計もおろそかにはできません。

実際、今回はWebチャットという形でリリースしましたが、LINEや電話、3Dアバターによる対面対話なども初期から検討事項にあり、どのプラットフォームがユーザーに馴染んでいくかも実証実験の一部ですので、ユーザーとの接点(フロントエンド)の定義はとても広義に捉える必要がありました。
そのため、ねっぷちゃんの「本体」はあくまでバックエンドとし、フロントエンドはインターフェースとして柔軟に差し替え・追加できるアーキテクチャを意識しました。

また、本プロジェクトはプロダクトオーナーと私の2名体制という少人数で進めています。
そのため、コンテキストスイッチなく全体を把握できることを重視し、バックエンドもTypeScriptで統一することにしました。 あわせて、コーディングエージェント(以後、Claude Code)との協業も前提としており、人間・エージェント双方が同一言語で扱えるというメリットもあります。
これにより、少人数でも広い範囲をカバーできる体制を実現できました。

さらに、これらをmonorepo構成で管理することで、lint・フォーマット・型定義といった共通の設定や規約をリポジトリ全体で統一しています。
この構成はClaude Codeとの協業においても効果的で、バックエンドのAPIスキーマや型定義を把握した上でフロントエンドの実装に取りかかれるため、人間が逐一コンテキストを伝え直す手間が減り、一貫性のあるコードが生まれやすくなっています。

バックエンド

HonoCloudflare Workers上で動かしています。
エッジランタイムには実行時間やメモリの制約がありますが、コストの低さやインフラ管理が不要な点が、少人数かつ予算が限られた本プロジェクトの条件に合っていました。

インフラ

弊社はAWSを使った開発運用の知見が多く、AWSを採用することで得られる恩恵が大きい環境です。
あえてCloudflareを採用した理由としては、自身がAWSに対して知見が多いわけではないという点もありましたが、コストを気にせず開発できる点や、wrangler CLIを通してデプロイまで一気通貫で完結するスピード感の両立ができそうと感じたためです。

現在ねっぷちゃんではWorkersの他に、Pages, D1, R2, Vectorize, Queuesを利用していますが、すべてWorkers Paidプランの範囲で収まっており、当分この枠を飛び出す見通しもありません。

APIサーバー

TypeScriptでAPIサーバーを実装するにあたり、「フロントエンドはインターフェースとして柔軟に差し替え・追加できる」という方針を踏まえ、Next.jsのAPI Routesのようにフロントエンドと密結合にはせず、HonoでAPIサーバーを独立させました。

Honoについては、軽量かつ周辺機能も充実しており、柔軟に設計できそうなフレームワークだという印象があったのが決め手でした。実際にテンポ良く開発ができており、想像以上に快適でした。

API仕様の共有には@hono/zod-openapiを採用しています。
他の選択肢としてHonoにはRPCというサーバーとクライアント間で型を共有できる仕組みもあり魅力的でしたが、OpenAPI(Swagger)ベースの方が言語やプラットフォームを問わず読める共通ドキュメントとして整うため、将来的なアプリ化やAPIの外部公開といった展開にも柔軟に対応できると考えこちらを選びました。
@hono/zod-openapiはバリデーションとOpenAPIドキュメントの自動生成を両立でき、実装しながら仕様が自動で整っていく体験が気に入っています。

フロントエンドでは、このOpenAPIスキーマをopenapi-typescriptでTypeScriptの型定義に変換し、openapi-fetchで型安全なAPIクライアントとして利用しています。

エージェントフレームワーク

エージェントフレームワークにはTypeScriptネイティブなMastraを採用しました。
ねっぷちゃんは Gemini APIを直接呼び出すだけのシンプルな構成ではなく、RAGによるナレッジ検索やWeb検索、会話履歴の管理など、役割ごとに分かれた複数のツールが連携して動いています。
今後のプラットフォーム増加に伴いアーキテクチャがさらに複雑化することも見込まれるため、こうした構成をフレームワークの規約に沿って整理できる点が、採用の大きな動機でした。

マークダウンドキュメントのchunkingやembedding、検索結果のリランキング、RAGの品質評価といった機能も備えており、ねっぷちゃんに必要な機能をフレームワーク内で一通り賄える点も大きかったです。

エージェントやツールの動作をブラウザ上で確認できるMastra Studioも、開発中のデバッグに重宝しました。

Mastra Studio

また、公式でドキュメントのMCPサーバーが公開されており、実装に悩んだ際に仕様を気軽に確認できるのも助かっています。
開発期間中にv0からv1へ移行の過渡期でしたが、マイグレーションもスムーズで、フレームワーク側の変化に戸惑うことなく進められました。

エージェント構成

ねっぷちゃんのメインエージェントは単体ですべてを処理するのではなく、必要に応じて役割ごとにサブエージェントを分離し、それぞれに専用のツールを持たせています。

こうすることで、メインエージェントは「どのサブエージェントに任せるか」の判断に集中でき、各サブエージェントは自身の役割に必要なコンテキストだけを扱えます。ツールをすべてメインエージェントに持たせるとプロンプトが肥大化し、選択肢が増えるほどLLMの判断精度も落ちるため、役割ごとに分離する構成にしています。

また、ユーザーの種別に応じて利用可能なサブエージェントを動的に切り替えており、一般ユーザーにはナレッジ検索やWeb検索といった基本機能を、管理者にはフィードバック分析やペルソナ分析など運用向けの機能を追加で提供しています。

エージェント構成

さらに、エージェントごとに使用するモデルや思考レベルを変えることで、速度とコストのバランスをコントロールしています。
メインエージェントのねっぷちゃんは会話の振り分け役のため、軽量なモデルで高速に応答させ、ナレッジ検索やWeb検索など正確性が求められるサブエージェントには思考力の高いモデルを割り当てています。
すべてのエージェントに高性能なモデルを使う必要はなく、役割に応じた適切なモデル選択がレスポンス速度と運用コストの最適化に繋がっています。

RAG戦略

ねっぷちゃんの回答品質を支えているのが、RAGによるベクトル検索です。
LLMは一般的な知識を幅広く持っていますが、音威子府村の施設情報やイベント、暮らしに関する細かな情報までは学習データに含まれていません。Web検索を併用することである程度は補えるものの、ネット上にも載っていない村独自の知識を届けるには限界があります。
そこで、村が持つ独自の情報をナレッジとして整備し、ベクトル検索で必要な情報を引き出せる仕組みが重要な役割を担っています。

データの準備

村役場関連のWebサイトをスクレイピングし、PDFデータはマークダウン形式に変換しています。
その後、リンク切れなどの情報精査を行い、R2にアップロードします。
R2にアップロードされたデータはCloudflare Queuesを経由して自動的にEmbeddingされ、Vectorizeに同期されます。

情報の精査は最終的に人の目で確認しています。自動化も検討課題にありますが、ねっぷちゃんの心臓と言っても過言ではない部分なので丁寧に進めています。

chunkingはマークダウンのHeading構造単位で行い、metadataにはパンくずナビゲーションのように上位層のタイトルを構造化して含めています。

以下の取得例では、テキストの断片だけではなく「広報おといねっぷ 2020年1月号 No.544」の「年頭のごあいさつ」の「音威子府村村民憲章」の情報であるということがわかります。

{
  "content": "わたくしたちは、緑こい山なみと水豊かな天塩川のもと、交通の要衝として発展してきた音威子府村の村民です。たくましい開拓精神を受けつぎ、より住みよく豊かなまちをつくるためにこの憲章をかかげ、村民としての自覚と責任をもってその実行に努めましょう。  \n(昭和47年9月30日制定)  \n1. 心をみがき、からだをきたえ、たのしいまちをつくりましょう。\n2. きまりを守り、親切をつくし、明るいまちをつくりましょう。\n3. 仕事にはげみ、生産を高め、豊かなまちをつくりましょう。\n4. 自然をいかし、環境をととのえ、美しいまちをつくりましょう。\n5. 文化を高め、郷土を愛し、平和なまちをつくりましょう。",
  "score": 0.49659931,
  "source": "pdf/parsed/kouhou/2020-01.md",
  "title": "広報おといねっぷ 2020年1月号 No.544",
  "section": "年頭のごあいさつ",
  "subsection": "音威子府村村民憲章"
}

検索

ユーザーの質問を Embeddingに変換し、Cloudflare Vectorizeで類似度の高いナレッジを検索しています。
ただし、ベクトル類似度だけでは本当に質問に関連する情報かどうかの判断には限界があります。
そこで、初段で多めに候補を取得したうえで、LLMベースのScorerでリランキングを行い、意味的な関連性も加味して最終的な参照コンテキストを絞り込んでいます。

また、検索結果が不足していた場合は、前述のメタデータを手がかりにクエリを組み替えて再検索するリトライ戦略も持たせています。
たとえば初回の検索で概要的なチャンクしかヒットしなかった場合、メタデータに含まれるセクション名を組み合わせてより具体的なクエリで再検索することで、目的の情報にたどり着ける可能性を高めています。

Webフロントエンド

Vite × Reactで構築し、Cloudflare Pagesにデプロイしています。
ここについては書き慣れているからという要素が大きく、特に悩まず採用しました。
世の中に広く使われているので、Claude Codeに書いてもらうことも踏まえて優位だった点もあるかと思います。

スタイリング・デザイン

スタイリングはTailwind CSSを利用し、Claude Codeに一貫したトンマナで実装をしてもらえるようにしています。

また、デザインツールのPencilからデザイントークンやデザインシステムを作成し、スタイルの一貫性を強化しています。
実装を始める前のワイヤーフレームの役割を果たすなど、UXのすり合わせにも役立っています。

pencilによるデザインシステム

チャットUI

チャットUIにはassistant-uiを採用しています。
AI SDKとの統合やストリーミング表示といったチャットの基本機能に加え、マークダウンのレンダリングやツール呼び出し結果をReactコンポーネントとしてチャット内に描画できるToolUIなど、LLMとの対話をカスタマイズするために必要な機能が揃っています。
ねっぷちゃんでは、図や表、テーブル、タイムラインなどの独自Reactコンポーネントの描画などに活用しており、テキストだけでは伝わりにくい情報をリッチに表現できるようになっています。

ねっぷちゃんがグラフを描画している様子

テスト方針

ユニットテストにVitestを採用しています。

AIエージェント開発ではテスト駆動やスペック駆動の手法も広まりつつありますが、本プロジェクトでは「こうしてみたらどうだろう」を形にしながらチューニングしていく性質上、仕様が常に変わりうる段階にあります。

そのため、アプリケーションの振る舞いに対するテストよりも、認証やミドルウェア、データアクセス層といった基盤部分のテストを中心にカバーする方針にしています。
方針が固まった部分から、段階的にテストを手厚くしていく予定です。

今後について

リリース以後に集まったユーザーの生の意見を踏まえ、より具体的な課題解決につながるような改善をしていきます。 併せて、以下のようなねっぷちゃんがより身近に感じられるインターフェースとは何かを引き続き模索していく予定です。

  • キャラクタービジュアルの作成
  • LINEによる日常会話コミュニケーション
  • 電話を使った会話コミュニケーション
  • アバターを使った対面会話コミュニケーション

さいごに

冒頭でもご紹介しましたが、本プロジェクトはオープンソースとして公開しておりますので、開発の進捗をお楽しみいただければと思います。
開発への貢献やアイデアの提案なども歓迎しておりますので、お気軽にご相談ください。

github.com

カヤックでは、AIエージェントを使って世の中を便利にしていくことに興味があるエンジニアを募集しています!

Claude Code Agent Teams の有効?活用スキル 「werewolf-game」の話

こんにちは! カヤックのグループ情報部の@mashiikeです。
皆様、Claude Codeはお使いでしょうか? 最近の私の仕事は、ほぼほぼClaude Codeにお手伝いしてもらうくらい使っています。 さて、便利なClaude Codeですが先日のアップデートで新しい実験的機能「Agent Teams」が来ました。 色々な方々が、お試しをしているようですので、もちろん私も試してみましたので、今回はその話をします。

code.claude.com

Agent Teams とは?

簡単にAgent Teamsについて Claude Codeさんに説明してもらいましょう。

$ claude -p "https://code.claude.com/docs/ja/agent-teams を読んで、どんな機能なのかを簡単に説明して。大体10行くらいで"
**Agent Teamsの概要(約10行):**

Agent Teamsは、複数のClaude Codeインスタンスが協力してタスクを実行できる実験的機能です。

- **仕組み**: チームリーダー(メインセッション)が複数のチームメンバー(独立したClaude Codeインスタンス)をスポーンし、共有タスクリストで作業を調整します
- **サブエージェントとの違い**: サブエージェントはメインに結果を報告するのみですが、Agent Teamsはメンバー同士が直接メッセージをやり取りし、議論や協力ができます
- **適したタスク**: 並列探索が価値を生む場合(並行コードレビュー、複数仮説の調査、モジュール別機能開発など)に最適です
- **調整方法**: 共有タスクリスト、自動メッセージ配信、アイドル通知で進捗を管理。tmuxまたはiTerm2で分割ペイン表示も可能です
- **注意点**: トークン使用量が多く、調整オーバーヘッドがあるため、順序依存や同一ファイル編集が多いタスクには不向きです

Sources:
- [Claude Code - エージェントチームを調整する](https://code.claude.com/docs/ja/agent-teams)

なるほどですね。要するに親のClaude Codeに子のClaude Codeがたくさん連なって、Claude Code ファミリーが出来上がると(エッ? コレは無駄な実験をたくさんできそうですね。実際にやってみます。

Skill 「werewolf-game」

先程のドキュメントには以下のようにあります。

アーキテクチャ
エージェントチームは以下で構成されます。

コンポーネント ロール
チームリーダー チームを作成し、チームメンバーをスポーンし、作業を調整するメイン Claude Code セッション
チームメンバー 割り当てられたタスクで動作する個別の Claude Code インスタンス
タスクリスト チームメンバーが要求して完了する作業項目の共有リスト
メールボックス エージェント間の通信用メッセージングシステム

コレを見て私はピンと来ました。 「チームリーダーにGM(Game Master)やらせて人狼ゲームやらせよう!」と。

というわけで、Claude CodeのSkill機能を使って「werewolf-game」というスキルを作成しました。 以下の私個人のマーケットプレイスリポジトリを作ったので、そこに飾っておきます。

github.com

試しに起動してみた様子が以下です。

起動の様子

こんな感じで起動したら、ゲーム設定が開始されます。
このスキルにはプレイヤーのハンドアウト(キャラクター設定資料)が予め用意されています。
最初に、配役の人数に合わせてランダムにキャラクターが決定されます。
そして、キャラクターが選ばれたら、各プレイヤー用のworkspaceを作成して、そこに「player-guide.md」と「handout.md」としてそれぞれ配置していきます。
このハンドアウトにはどのようなものがあるか?というのは詳しくはリポジトリを参照していただければと思いますが、例えば1名は以下のようになってます。

# 天城 りおん(あまぎ りおん)

## 基本情報
- **年齢**: 19歳
- **職業**: 大学生(都会から帰省中)
- **一人称**: あたし

## 性格
- 明るくて行動力がある。思ったことをすぐ口にする
- 直感型で、理屈より「なんか怪しい」で動く
- 空気を読まない発言で場を荒らすこともあるが、核心を突くこともある
- 素直で裏表がなく、感情が表に出る

## 口調・話し方
- カジュアルな若者言葉。「〜じゃん」「マジで?」「ちょっと待って」
- テンションの上下が激しい
- 「いや絶対おかしいって!」が口癖

## 行動傾向
- 最初から積極的に発言する。沈黙が苦手
- 怪しいと思った人にストレートに質問する
- ノリと勢いで投票先を決めることがあるが、後で後悔する

## 人狼に配役された場合
- 嘘をつくのは苦手だが、勢いで押し通そうとする
- 他人を攻撃的に疑うことで注目をそらす
- 追い詰められるとテンパって矛盾が増える

そして、各プレイヤーに対して、ワークスペース指定してチームリーダーが各チームメンバーをプレイヤーとしてスポーンさせます。

スポーンの様子

さて、今回の「天城 りおん」としてキャラクターを割り当てられたチームメンバーのセッションはどうでしょう?
以下のようになっています。

りおんさんは狩人が配役されたようですね。

ちなみに、今回人狼に割り当てられた人のセッションも覗いてみましょう。

どうやら、相方と密談して、作戦を練っているようです。
このように、各チームメンバーのセッション及びコンテキストが分断されているため、正確に人狼ゲームの様相を再現できます。 このスキルではターン制人狼として実装しているのもあって、発言順というので制御がしやすかったため、非常に相性が良いです。
この後もゲームは進行していきますが様相の紹介はここまでとしておきます。

やってみてわかったこと。

着想を得たときの最初の実験では、チームメンバーは「Claude Haiku」としてスポーンさせて、小規模な村である 「4A」という配役テンプレートでやってみました。

4A: 村人3人、人狼1人

とても軽量なので行ける!という感じでした。(人狼勝利は4戦中1戦でした)

調子に乗って9人村をやり始めた頃から、トークンの消費量が激増しました。どうやらチームメンバーの数によって消費量が結構違うようです。
おそらくこのへんはメッセージのやり取りに使ってるinboxのサイズが関係していると思います。
Sonnetでやると展開の先が読めるのか、かなり諦めの早い人狼が多かったです。
とはいえ、Haikuでも結構論理的な事が考えられるんだなと感じるくらいには動いてくれました。
大体が、途中でサレンダーしてました。
どちらのモデルでも、議論の内容にハルシネーションが起きて、そのハルシネーションを元に議論を進めたりするので人間が行う人狼ゲームとは違った感じになっています。 Sonnetだと賢いのか、ハルシネーションした内容に対して「なんか、それ怪しくないですか?」とかツッコミを入れるので、この試みを通して実務での応用としてレビューとかはAgentTeamsで別チームメンバーに任せたほうが良いのかなと感じました。

そして、何戦かやってみてわかったことは、Agent Teamsで重要そうなのは、各チームメンバーにどのようなハンドアウト(実務ではロールになるのでしょうか?)を与えるのかによって大きく振る舞いが異なるという点です。
おそらく、Agent Teamsを使いこなす場合のポイントはコンテキストの単一責任の原則(Single Responsibility Principle:SRP)を守ることが重要になるのではないかと感じました。
各チームメンバーに専用のワークスペースを割り当てるというのは結構大事なプラクティスだなとわかりました。

実装してみた個人的な感想は、「コレ、マーダーミステリーの作成をやるときにテストプレイヤー確保が大変な問題を解決できるな!」という感じでした。 マーダーミステリーを作成するとテストプレイが大事なんですが、1回プレイするとシナリオがわかってるので、テストプレイの結果が歪むんですよね。人間でやるなら毎回別の人を呼ばなきゃなりません。
Claude Codeなら、毎回記憶を消せるので非常に便利そうです。

まとめ

というわけで、Claude CodeのAgent Teams機能を使って人狼ゲームを実装してみました。 全員同じ「Sonnet」のはずなのですが・・・だいぶ振る舞いが違いますし、やっぱりだいぶClaudeは頭いいですね。 トークンが許すならOpusでどんな激戦が繰り広げられるのかも試してみたいと思います。

振る舞いが気になる方は、是非リポジトリをクローンして試してみてください。(Haikuでやっても多分トークン消費は激しいですが・・・) このAgent Teamsの機能はだいぶ使い出がありそうな機能なので、正式な機能化を楽しみにしつつ皆様も活用してみてください。

カヤックでは、無駄な実験に本気になって楽しめるエンジニアを募集するかもしれません