AI副村長「ねっぷちゃん」のロール設計と地域データ運用 — AI副村長をどう形づくったか

こんにちは、AI推進室の村井です。 先日、同じプロジェクトメンバーの大脇が、AI副村長「ねっぷちゃん」のアーキテクチャと技術選定について書きました。

techblog.kayac.com

ねっぷちゃんは、 GitHub の README にも書いた通り、AIを単なる道具として置くのではなく、その地域の文脈を持った存在として受け入れてもらえるのか。そんな仮説を実験しているプロジェクトです。 コンセプトとしては「存在するソフトウェア」── ただ機能するだけの道具ではなく、村の一員のようにそこにいると感じられる存在になれるかを試しています。

github.com

また、「AI副村長」という名前には、やがて村のムードや、今この村で起きていることを教えてくれる、村長のパートナーのような存在になっていけたら、という思いも込めています。

ねっぷちゃんが目指しているのは、ひと言でいえば「めっちゃ地元のAI」です。

では、そのねっぷちゃんの雰囲気は、どうやって生まれてきたのか。 なぜ「村のことに詳しいAI」と感じられるようになっていったのか。今回はその話を書いてみます。

実運用を通じて強く感じたのは、地域特化AIではシステムプロンプトだけでなく、参照するデータの質・鮮度・構造が、AIの印象や信頼感をかなり大きく左右する、ということでした。この記事では、ねっぷちゃんのロール設計を、プロンプト単体の話ではなく、データ設計や運用も含めた実践として振り返ります。

AIを理解するには作るしかないと思ったら、ホームコースが必要だとわかった話

AI推進室を立ち上げたのは、仮説というより、ほとんど直感からでした。
AIを理解するには、読むだけではなく、作るしかない。そう思ったからです。

HN、arXiv、X を読んでいるだけでも、もちろん面白いことはわかります。驚くべきことが起きているし、だから驚くし、同時に懐疑的にもなる。 でも少なくとも私は、読んでいるだけでは自分の手触りとしては掴みきれませんでした。

仲間たちと、いろいろなフィールドで生成AIを片っ端から環境をつくって試していく中で、だんだん考えが収束していき、ひとつの考えが生まれます。

AIはいずれ、あらゆるデジタル制作をかなりのところまで自動化してしまうだろう。でも、AIそのものを本質的に理解するには、触って、作って、失敗して、直すことを繰り返せる場所が必要だ。そう思うようになりました。

毎日試せる場所。
すぐに試して、すぐ壊して、また直せる場所。
それが必要だとわかってきて、自分の中ではそれを「ホームコース」と呼ぶようになりました。

では、そのホームコースをどこに置くのか。
ここで出てきたのが、「AIが全然関係なさそうな場所に置いた方が、かえって面白い研究や使い方が生まれるんじゃないか」という考えでした。田舎の村や畑、山の中。そういう場所です。

そこからつながったのが、北海道中川郡音威子府村(おといねっぷむら)でした。

なぜ600人の村なのか

音威子府村の人口は約600人です。

「小さすぎないか」とよく言われます。
でも、私たちにとってはこのサイズが重要でした。

いきなり大規模なものを作るのではなく、コンパクトに、クイックに試していける。住民ひとりひとりの声が直接届く距離感で、手応えも速く返ってくる。最小単位で動かして確かめる、という進め方に向いていたのです。

そしてもうひとつ、この村にはチャレンジを受け入れてくださる土壌がありました。村長の「やってみましょう」という返事の軽やかさと、受け入れてくださる村の方々の懐の深さが、このプロジェクトを動かした最大の要因のひとつでした。ソフトウェア開発の感覚で言えば、非常に貴重な実証環境でした。ご協力にはいつも感謝しています。

さらに、この村には全国でもかなり特徴的な構造があります。

600人の村で、10代が約100人。人口の中で10代後半が大きな比率を占めています。これは北海道おといねっぷ美術工芸高校があるためで、役場でヒアリングした際にも「関係者の半分が学校関係者」と言われるほど、この高校が村の構造に影響を与えています。

実は、この事実が、ねっぷちゃんのロール設計にかなり直結していきます。

「17歳女性」という設定は、最初のロール設計から始まった

ねっぷちゃんはメディア上で「17歳の女性AI」と紹介されました。ここはどうしてもキャラクター設定として受け取られやすい部分なんですが、実際には、まずユーザーペルソナを起点にしたロール設計として導入しています。

プレスリリース後、各メディアから最も多く受けた質問のひとつが、「17歳女性という設定はなぜそうなったのですか?」でした。考えたのは企画している私ですが、これには明確な理由があります。

それは、誰が一番自然に話しかけてくれそうか、ということを起点に考えたからです。

なぜ17歳か。
音威子府村では、高校生がデジタルなインターフェースに最も自然に触れてくれる層のひとつだと考えたからです。最初に気軽に話しかけてくれる可能性が高い相手に合わせて、会話のトーンや距離感を設計した。その初期条件のひとつが「17歳女性」というロールでした。

ここで意識したのは、柔らかい話し方で、軽やかなキャラクター、明るく前向き、という方向性です。役場の案内をする以上、情報は公式であるべきですが、接し方まで役所的に硬くする必要はない。むしろ、地域の人が最初の一言を投げやすいことの方が重要でした。

実装上は、"あなたは北海道音威子府(おといねっぷ)村に住む17歳の女の子「ねっぷちゃん」"というロール指示をシステムプロンプトに含めています。ただ、年齢や属性はあくまで対話のトーンを整えるためのものであって、「村のことに詳しい」と感じられる理由の中心はそこではありません。

むしろ実際には、参照する地域データが整備され、それが適切に取り出されることによって、今の「軽やかで明るいが、地域文脈を持ったAI」という印象が形作られていきました。世界観を先に厚く決めたというより、実際の利用シーンと地域データの整備からロールが定着していった、という順番に近いです。

なぜ最初に"普通のRAG"を選んだのか

当時、選択肢はいくつもありました。素直なベクトル検索でいくのか、BM25 を含むハイブリッド検索に寄せるのか、GraphRAG のように関係性をたどる取り出し方を試すのか。Mastra 側でもグラフベースで探索するRAGの方向性が出てきていて、そこも気になっていました。

でも、どれが一番勝ち筋なのかは、正直さっぱりわかりませんでした。

その中でRAGを選んだ理由は、まず前のプロジェクトでの経験です。RAG を使った知識抽出やセマンティック検索を実装した経験があり、その礎がありました。そこに Mastra.ai の当時のアーキテクチャを合わせて、まず立ち上げるのが現実的でした。

実装としては、シンプルなベクトル検索を軸にしつつ、取得した候補に対して軽いリランキングをかける構成です。最初にベクトル検索で関連する文書チャンクを取得し、その後にクエリとの意味的な一致度などをもとに再スコアリングして、最終的に上位のコンテキストをLLMに渡しています。いわゆる大掛かりなグラフ構造や複雑な推論はまだ入れていませんが、検索→再評価という二段階の構成で retrieval の安定性を上げる形です。

もうひとつは、優先順位の問題です。 村の情報を一通り投入したときに、どの程度の性能が出るのか。embedding にどれくらいのコストがかかり、1回の会話あたりどれくらいのランニングコストになるのか。どこまでデータ整備が必要で、どこまでセマンティック検索だけでいけるのか。そこがまだ見えていませんでした。

つまり当時は、最適解を選ぶより、最初のイテレーションを最速で回せる構成を選ぶ方が重要だったのです。

これは賛否がある判断だと思います。評価設計をもっと先に詰めるべきだった、という意見も理解できます。ただ、自分の優先順位は「実ユーザーが触る状態を作ること」でした。数値を先に揃えるより、まず使われる状況を作って、そこから問題を把握したかった。

その後の数ヶ月でも、AI技術はどんどん更新されていきました。Graph RAG の精度が上がった、新しいモデルが出た、新しい手法が気になり始めた。目移りはずっとあります。今もあります。

ただ、乗り換えるかどうかは、村で実際に使われながら判断する、というスタンスを保っています。Open R&D の醍醐味はそこにあると思っています。必要なら来週、新しい手法を実装して試すこともできます。でも、それをやる理由は「流行っているから」ではなく、「今ある現場の失敗に効くかどうか」です。

地域データの質が、AIの印象と信頼感を大きく左右した

「ねっぷちゃんは村のことに詳しい」と感じられる状態は、システムプロンプトだけでは作れませんでした。少なくとも今回の実装では、参照される地域データの質と、その取り出し方の設計が大きく効いています。

最初期のシステムプロンプトには、演技指導のような細かいキャラクター指示がたくさん入っていました。MVP以前の手元デモの段階では、設定的な文言も入れていた記憶があります。

でも、実際に運用を始めると問題が出ました。回答精度に悪影響が出たり、軽すぎる返答になったり、文脈と関係のないことを言い出したりする。書きたくはなりますが、詳しいと書いたから詳しくなるわけがないのです。むしろ、厚い演出がノイズになる場面がありました。

ハーネス実装を担当している大脇が、そうした要素をどんどん削っていき、私も「ハルシネーションの原因になるなら削ろう」と了承し続けた結果、今のねっぷちゃんには、かなり薄いキャラクターレイヤーだけが残っています。

これをシステムプロンプトの限界と呼ぶこともできるかもしれません。
でも私には、プロンティングとコンテキストがそれぞれどこで効いているのか、その役割の違いが前よりずっと見えやすくなった過程に思えました。

少なくとも今回のプロジェクトでは、キャラクターを厚く見せることをプロンプトだけで担うよりも、地域の公式情報をどう集め、どう整え、どう取り出せるようにするかの方が、「このAIは村のことをわかっている」という感触に直結していました。

村役場の Web サイトをスクレイピングし、PDF を Markdown に変換し、リンク切れや情報の古さを確認し、チャンキングして embedding し、Vector Store に格納する。この一連のデータ処理は、インフラやフレームワークの選定と同じくらい、設計の仕事です。

実際にはこの部分も単純ではなく、自治体のPDFには表や段組みが含まれていることが多いため、単純なパーサーだけではなく変換用にエージェンティックに整形してMarkdown化しています。その後、Markdownの見出し構造をもとにチャンクを分割し、短すぎるチャンクは除外しベクトル化するようにしています。

どのデータを取り込むか。
どう構造化するか。
鮮度をどう保つか。

この判断ひとつひとつが、RAG の retrieval 精度だけでなく、最終的な回答の質や信頼感を決めていきました。

何を見ながら改善していたのか

では、何を見ながら改善していたのか。
現時点では少なくとも、次の3点を重視しています。

1つ目は、事実として正しいか
役場の案内や収集日など、地域の生活に関わる情報では、自然な言い回しよりもまず正確であることが重要です。

2つ目は、情報が最新か
自治体の情報は、同じようなタイトルの PDF でも年度が違うことがあり、検索結果の上位に古い情報が出てくることがあります。地域AIでは、正しそうに見える古い情報を答えてしまうことが特に危険です。

3つ目は、地域文脈に接続できているか
たとえば曖昧な質問に対して、一般論ではなく「この村の話として」返せているかどうかです。

similarity、context precision、context relevance、hallucination といった一般的な評価値も可視化して確認しています。もちろん、こうした指標は改善の方向を見るうえで重要です。

ただ、少なくとも今の実装では、最終的な回答品質により大きく効いているのは、評価値の微調整そのものより、どのデータを取り込み、どう整え、どう新しい状態に保つかというキュレーションの方だと感じています。

失敗した問い合わせは、できるだけそのままテストケースとして残し、データ更新や変換処理の修正後に再確認しています。まだ十分に自動化された評価基盤とは言えませんが、少なくとも「どこで間違えたか」「何を直したか」を追える状態にしていくことは意識しています。

具体的には、失敗した問い合わせを固定のテストケースとして蓄積し、同じ質問を複数回実行して pass@k(1回でも正解すればOK) と passk(すべて正解した場合のみOK) の両方で安定性を見るようにしています。 各ケースには「正しく答えるべき質問」だけでなく、「情報が不足しているため推測せずに"わからない"と答えるべき質問」も含めています。ゴミ収集日のように誤答が生活に影響するものは後者に分類し、回答内容のキーワード判定や類似度の閾値を使った簡易的なグレーディングで確認しています。

まだ研究論文のような評価とは言えませんが、少なくとも 同じ失敗をもう一度起こさないためのテストセット としては機能し始めています。

皆さんどうやってるんでしょう。
地道な調整が効く世界にも見えていますし、イテレーションループの暴力で改善できる世界にも見えています。私もまだ答えはありません。ただ、この領域では仮説を語るより、失敗を評価セットに戻し続けることが今のところ一番効いています。

ゴミの日を間違えた失敗を改善する

実証実験を始めて数日で、住民の方から「ゴミの日が違う」という指摘をいただきました。

確認すると、村のデータにはゴミ収集カレンダーの PDFリンク があったのですが、表形式でパース精度が低いのか、ツールの不発か、うまくいかず、さらにブラウザツールが検索すると上位に出てくるのが一昨年のカレンダーだったため、まったく違う日付を答えていました。

この失敗は象徴的でした。
地域AIでは、「それっぽく答える」ことよりも、「古い情報を拾わない」ことの方がずっと大事です。

じゃあ最新のカレンダーをちゃんと扱えるようにしないとね、ということでデータを見直し、Markdown 変換後の扱い方も修正し、評価用のテストセットにその失敗ケースを加えて再確認しました。

派手な改善ではありません。
でも実際には、こういう地道な修正の積み重ねが効いています。

この手仕事をどこまで自動化できるか、どこまで精度高く維持できるかは今後の課題です。2026年1月時点では、少なくとも「どのデータを取り込むべきか」の初期判断は、まだ人手でやる方が品質が高いと感じていました。ただ、3月の今はもう違い始めている気もしていて、この領域の変化の速さを感じています。

gemini-embedding-2-preview のような新しい選択肢が出てくると、ゲームチェンジの匂いもします。とはいえ、新しい手法に飛びつく前に、まず目の前の失敗をどう潰すかの方が、今の現場では重要です。


住民の質問が、地域AIを育てていく

テクノロジーに詳しい必要のない、普通の人たちが、日常の中でどうねっぷちゃんに話しかけるのか。実証実験を通じて、その答えが少しずつ見えてきています。

「新しい道ができるらしいね?」
「今日は何のゴミの日?」
「テンが出た、どうしよう?」
「村長さんはどんな人?」

難しいことは聞いていません。
全部、生活の延長線上の質問です。

でも、相手はLLMです。その質問だけではコンテキストが足りない。
どの道の話なのか。
どの自治体での話なのか。
どの地域の文脈なのか。

その不足している前提を、「この村のAIである」という条件が補ってくれる。ここに地域AIの面白さがあります。

たとえば「テンが出た、どうしよう?」という質問。
私は最初、テンという単語を聞いても、この辺りの動物なんだろうな、くらいしか思いませんでした。

この質問が来たとき、ねっぷちゃんは村の2020年の広報誌まで遡って回答しました。村の公式な鳥獣被害防止計画の主要対象はヒグマ・エゾシカ・アライグマで、テンは中心対象ではない。ただし捕獲記録はある。テンは雑食で柔らかい野菜を狙うことがある。早めの収穫が有効——という内容です。

これは FAQ 的に最初から想定していた質問ではありません。
でも、村のナレッジが広報誌レベルまで入っていたことで、地域文脈を持った返答ができた。

RAG の retrieval として見ても、こういうクエリは面白いです。
事前に全部の質問を設計しきるのではなく、住民の自然な問いが、システムの弱いところと強いところをあぶり出してくれる。そこに、ホームコースとしての価値があります。

住民の方々が、ねっぷちゃんを介して村のことをAIに語りかけていく。その蓄積が、いつか村全体のムードの可視化や、村長さんが「今、村の人たちは元気か」を知る助けになるかもしれない。

人とAIが仲良くなること自体がゴールではありません。
AIを介して、人と人が仲良くなること。今はそこを目指して、AI副村長という形をアップデートしていっています。

なぜオープンR&Dなのか

ソースコードを公開している理由を一言で言えば、仲間探しです。

ChatGPT のような高性能な汎用AIが誰でも月数千円で使える時代になりました。でも、それを「その地域専用のAI」として育てていくには、まだ技術的な壁があります。だからこそ、地元のAIは、地元の人の手が入っていた方がいい。地元のエンジニアがやっていたらなおさらいい。

同じような動きが、全国の村や町で起きてほしいと思っています。

オープンにしているのは、完成品を見せたいからというより、「こういう課題がある」「ここはまだ弱い」「こうすると良くなるかもしれない」という知見を、もっと往復可能なものにしたいからです。面白いアイデアがあれば、コードを書いて PR を送ってもらえたらうれしいですし、逆に他地域での知見をこちらも学びたいと思っています。

これから

役場で作業をしていたとき、ガラケーを持ったおじいちゃんが、役場の人に質問しに来ていました。 その光景を見たとき、ねっぷちゃんが答えられることを、そのおじいちゃんは電話で聞けるようになるべきだと思いました。

役場に行けば物理的にねっぷちゃんに会える。
そこまで作れたら、ひとつのゴールだと思っています。

インターフェースの拡張として、村の公式LINEアカウントや電話番号を持たせて音声通話で会話できるようにしたり、フィジカルなボディを与えて村役場に置いたり、複数のチャネルにまたがるインタラクションの形を計画・検討しています。高齢者が多い地域では、Webチャットだけでは情報が届かないケースは十分にあります。すべての人がアクセスできるようにするには、物理側もマルチモーダルな展開が必要です。

データについては、住民のみなさんのニーズに寄り添いながら、随時ブラッシュアップしていますし、情報の精査は、今も最終的には人の目で確認しています。自動化ももちろん検討していますが、ここはねっぷちゃんのコアと言っていい部分なので、慎重に進めるつもりです。

おといねっぷ美術工芸高校の生徒さんたちとのコラボレーションも、いつかやってみたいと思っています。ねっぷちゃんが作品の一部になったり、逆にそこから新しい表現のエッセンスをもらったり。ロールもデータも、育っていく余地はまだたくさんあります。

さいごに

本プロジェクトはオープンソースとして公開しています。
開発への貢献やアイデアの提案なども歓迎しておりますので、お気軽にご相談ください。 github.com

技術的なアーキテクチャや実装の詳細は、大脇の記事もあわせてご覧ください。 techblog.kayac.com

このプロジェクトを通じて見えてきたのは、地域AIのふるまいは、プロンプトだけで決まるものではないということでした。少なくとも実運用では、どんな地域データを持ち、どう更新し、どう取り出せるようにするかが、回答の精度だけでなく、AIへの信頼感や「この地域のことをわかっている」という印象そのものに強く影響します。

その意味で、地域AIの設計は、モデル選定やUI設計だけではなく、データ整備と運用設計を含めて初めて成立するのだと思っています。

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

音威子府村の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エージェントを使って世の中を便利にしていくことに興味があるエンジニアを募集しています!