NotebookLMへのカスタム指示やIDEのMCP Server連携を用いてドメイン特有の質問にAIが回答できるようにする
はじめに
こんにちは、@cornet_higashi です。
近年の AI の進化は本当に目覚ましいですね。
幅広く役立てていきたいところですが、新しい情報になかなか追従するのが難しくなってきています。
今回の記事では、開発を含めた業務においてドメイン特有の知識をAIに与えてやることで、欲しい回答を手軽かつ正確に取得できるかどうかを試してみました。
前提
今回紹介する話に関係するツールや概念について事前に簡単に説明します。
NotebookLM
Googleから提供されている、AI搭載の情報整理ツールです。
複雑な環境構築や準備作業を必要ともせず、質の高い高度な分析を行うことができます。
連携される情報についてはプライバシーポリシーに目を通しておきましょう。
カスタム指示
AIからの応答や出力形式について、事前に指定できる機能です。
分かりやすい例では、AIアシスタントの口調やキャラクターを変えるみたいなものが挙げられます。
また、Github Copilotに対してカスタムで指示や情報を事前に与えるための機能もあります(copilot-instructions)
これを用いて開発におけるレギュレーションや注意事項、事前知識(リポジトリ構成やアーキテクチャなど)を指定することにより、コード生成の精度を向上させることが可能です。
MCP(Model Context Protocol)
AIモデル(LLM)が外部のデータソースやツールとコンテキストを共有し、シームレスに連携するための標準プロトコルです。
LLMは通常ではリアルタイムの情報や特定のスコープでしか公開されていないデータ、外部ツールとは情報の受け渡しができないため、そういった用途を実現するためにMCPが使われます。
課題
開発作業や業務における疑問点などを Gemini をはじめとした AI アシスタントに投げて回答させるのが日常的になっていますが、ある限られたドメインの用語や知識を元にした質問は基本的に想定した回答を得ることができません。社内や特定の集まりでは普通に使っている用語だが、実際には一般的ではないみたいなケースもよくあるかと思います。
そういった用語に関連した質問をAIに投げて回答を得るには、都度用語に関する詳細な情報を与えてやらなければなりません。これは非常に手間がかかりますし、インプットの精度によっては回答の精度も下がってしまいます。
自然言語の一部としてドメイン特有の用語が使えるようになると、AIとのやり取りを行う上での精度や効率が向上する可能性があります。
やったこと
今回のアプローチは、事前知識として管理しているドキュメントをAIに与えた上で質問に回答させる
といった内容です。以下の2つについて試してみました。
- NotebookLM にカスタム指示を与える
- Github Copilot & 自前の MCP Server を連携させる
NotebookLM にカスタム指示を与える
NotebookLMの基本的な使い方としてはソースとなる情報を複数登録し、その内容について質問すると回答・解説を得られるというものでした。
ここではGoogle Document内にカスタム指示を記述し、その情報をソースとしてNotebookLMに回答させる
ということをやってみます。これにはいくつかのメリットがあります。
知識の管理が容易になる
知識をGoogle Documentで管理できることで、管理が容易になります。
単なるテキストと比較して表現方法も豊富であるため、人にとっても見やすいものとなります。
複数のNotebookLMで知識を共有できる
標準のカスタム指示機能の場合は各NotebookLM毎に記述が必要ですが、一つDocsを作っておけば複数のNotebookLMから利用が可能となります。
用語集に追加・変更があった場合の対応も容易です。
開発者以外にも優しい
IDEなどを使わないため、開発者以外のメンバーなどGithubのアカウントを持っていない場合にも利用可能です。
何より、手軽に使えることと無料でできる範囲が広いという点が最大のメリットです。
それでは活用例について順に見ていきます。
活用例その1: TypeScript のコードを生成させる
一般によく使われている、AI にコードを書かせる作業をやらせてみます。
まず前提として、開発しようとしているサービスにおいて以下のような用語があったとします(内容は適当です)。
* ねこ時間
- 平日の12:00〜13:00のこと
* マタタビショット
- マタタビ200[mg]をねこに投与すること
* 茶トラ制約
- 茶色のトラ猫には100[mg]しかマタタビを与えてはならないという制約
これらを仮に「猫沼に浸かっているクラスタのドメイン(領域)における用語」とします。
当然、一般人には通じません。AIも知っている訳がありません。
これをまずGoogle Documentに登録してやります。
次に、NotebookLMで当該のDocsを読み込んでやります。
用語集の情報が展開されましたね。
その上で、以下のような指示を NotebookLM の Chat に投げてみます。
typescriptで関数を作成してください
- 現在時刻(Date)と猫({ name: string; kind: "キジ" | "黒" | "茶トラ"; addMatatabi: (count: number) => void})を与えると、ねこ時間に限りマタタビショットする
- ただし、茶トラ制約を考慮する
以下のような回答が帰ってきます。
はい、承知いたしました。ご提供いただいた情報に基づき、TypeScript でマタタビを投与する関数を作成します。
この関数は、現在時刻と猫の情報を基に、以下の条件を満たした場合にマタタビを投与します。
- ねこ時間 (Neko Time) に限りマタタビをショットする。
- ただし、茶トラ制約 (Chatora Constraint) を考慮する。
以下に TypeScript 関数とその説明を示します。
type Cat = {
name: string;
kind: "キジ" | "黒" | "茶トラ";
addMatatabi: (count: number) => void;
};
/**
* 現在時刻と猫の情報を基に、ねこ時間に限りマタタビを投与する関数。
* 茶トラ制約を考慮します。
* @param currentTime 現在時刻 (Datetime)
* @param cat マタタビを投与する猫のオブジェクト
*/
function administerMatatabi(currentTime: Date, cat: Cat): void {
// 1. 現在時刻が「ねこ時間」であるかを確認します。
// 「ねこ時間」は平日の12:00〜13:00のことと定義されています。
const dayOfWeek = currentTime.getDay(); // 日曜日: 0, 月曜日: 1, ..., 土曜日: 6
const hour = currentTime.getHours();
// 平日(月曜日から金曜日)であるかを確認
const isWeekday = dayOfWeek >= 1 && dayOfWeek <= 5;
// 時間が12時台(12:00:00から12:59:59まで)であるかを確認
const isInNekoTimeWindow = hour === 12;
const isNekoTime = isWeekday && isInNekoTimeWindow;
if (isNekoTime) {
let matatabiAmount: number;
// 2. 投与するマタタビの量を決定します。
// 「マタタビショット」はマタタビ200[mg]をねこに投与することと定義されています。
// しかし、「茶トラ制約」として、茶色のトラ猫には100[mg]しかマタタビを与えてはならないという制約があります。
if (cat.kind === "茶トラ") {
matatabiAmount = 100; // 茶トラ制約による
console.log(
`${cat.name} (${cat.kind}) は茶トラ制約のため、マタタビを${matatabiAmount}mg投与します。`
);
} else {
matatabiAmount = 200; // マタタビショットの定義による
console.log(
`${cat.name} (${cat.kind}) にマタタビショットとして${matatabiAmount}mg投与します。`
);
}
// 猫にマタタビを投与します
cat.addMatatabi(matatabiAmount);
console.log(`マタタビが${cat.name}に正常に投与されました。`);
} else {
// ねこ時間ではない場合、マタタビは投与されません。
console.log(
`現在はねこ時間ではありません(平日12:00〜13:00)。マタタビは投与されませんでした。`
);
}
}
// 関数の使用例(動作確認のためのサンプル)
// ダミーのaddMatatabi関数を持つ猫オブジェクト
const dummyCatFactory = (
name: string,
kind: "キジ" | "黒" | "茶トラ"
): Cat => ({
name,
kind,
addMatatabi: (count: number) => {
console.log(`DEBUG: ${name}がマタタビ${count}mgを受け取りました。`);
},
});
// 現在時刻がねこ時間内の場合(例: 月曜日12:30)
console.log("\n--- ねこ時間内の投与テスト ---");
const nekoTimeDate = new Date();
nekoTimeDate.setFullYear(2023, 0, 8); // 適当な月曜日
nekoTimeDate.setHours(12, 30, 0, 0); // 12時30分
const chatoraCat = dummyCatFactory("ミケ", "茶トラ");
const kijiCat = dummyCatFactory("タマ", "キジ");
administerMatatabi(nekoTimeDate, chatoraCat);
administerMatatabi(nekoTimeDate, kijiCat);
// 現在時刻がねこ時間外の場合(例: 土曜日14:00)
console.log("\n--- ねこ時間外の投与テスト ---");
const outOfNekoTimeDate = new Date();
outOfNekoTimeDate.setFullYear(2023, 0, 6); // 適当な土曜日
outOfNekoTimeDate.setHours(14, 0, 0, 0); // 14時00分
administerMatatabi(outOfNekoTimeDate, chatoraCat);
// 現在時刻がねこ時間外の場合(例: 平日11:00)
console.log("\n--- ねこ時間外の投与テスト(時間外) ---");
const beforeNekoTimeDate = new Date();
beforeNekoTimeDate.setFullYear(2023, 0, 8); // 適当な月曜日
beforeNekoTimeDate.setHours(11, 0, 0, 0); // 11時00分
administerMatatabi(beforeNekoTimeDate, kijiCat);
関数の説明
上記の
administerMatatabi
関数は、以下のロジックで動作します。
- 「ねこ時間」の判定
- 関数が受け取る
currentTime
オブジェクトから、曜日と時間を取得します。- 「ねこ時間」は平日の 12:00〜13:00と定義されています。
getDay()
メソッドで取得した曜日が 1(月曜日)から 5(金曜日)の間であること、かつgetHours()
メソッドで取得した時間が 12 であること(つまり 12 時台)を確認します。これらの条件を両方満たす場合のみ「ねこ時間」と判断されます。
マタタビ投与量の決定
- 現在が「ねこ時間」であると判断された場合、猫の種類 (
cat.kind
) に応じてマタタビの投与量を決定します。- **「茶トラ制約」により、「茶トラ」の種類の猫には100[mg]**のマタタビしか与えられません。
- 「キジ」や「黒」など、他の種類の猫には、通常の**「マタタビショット」の量である 200[mg]**が投与されます。
マタタビの投与
- 決定された量のマタタビを、引数として渡された猫オブジェクトの
addMatatabi
メソッドを通じて投与します。この関数は、提供されたソースの「ねこ時間」、「マタタビショット」、「茶トラ制約」の定義に厳密に従って実装されています。
このような感じで、事前に与えたドメイン知識を用いて回答を作ってくれます。
活用例その2: 集計のための SQL クエリを書かせる
今度は更に AI に頑張ってもらいましょう。
活用例その1はまだ辛うじて推測が可能な内容となっています(実際にGeminiに投げてみたところ、細部はともかくとしてそれっぽいコードを出力してきました)
次のお題はこれです。
もげがふげかもふもふして
全くもって意味不明ですね。
ちなみにこのプロンプトを Gemini に投げてみると、下記のような回答が帰ってきます。
お前は何を言っているんだという反応です。まぁ当然ですね。
今回必要となる知識は以下の通りです。
* もげ
- 商品の売上において、売上が1,000,000を下回る日が月の半分以上存在することを指す業界用語
* ふげ
- 事象が購入者の減少によって発生しているという理由を指す業界用語
* もふもふ
- 生産管理データベース上の商品売上テーブル/購入者管理テーブルにおいて、原因を調査するためのSQLクエリを出力すること
* 商品売上テーブル
- 以下のような構成のテーブルのこと
- id: ユニークID(string)
- sales: 日あたりの売上(number)
- sales_quantity: 日あたりの売上個数(number)
- date: 売上日(date)
* 購入者管理テーブル
- 以下のような構成のテーブルのこと
- id: ユニークID(string)
- count: 日あたりの購入者数
- date: 売上日(date)
では上記の業界用語をNotebookLMに理解してもらい、回答させてみましょう。
はい、「もげがふげかもふもふして」というご依頼ですね。承知いたしました。
これまでの会話とソースに基づき、「もふもふ」について詳しくご説明し、その実践方法を提示します。
「もふもふ」とは、生産管理データベース上の商品売上テーブルや購入者管理テーブルにおいて、原因を調査するための集計する SQL クエリを出力することを指す業界用語です。
この「もふもふ」は、特に「もげ」という現象(商品売上テーブルにおいて、売上が 1,000,000 を下回る日が月の半分以上存在すること)が発生した場合に、その原因を特定するために行われます。そして、その原因の一つとして「ふげ」(事象が購入者の減少によって発生しているという理由)が考えられるため、「もふもふ」によって「ふげ」の有無を調査することが重要になります。
「もふもふ」を行う際に使用するテーブルは、以下の構成です。
- 商品売上テーブル
id
: ユニーク ID (string)sales
: 日あたりの売上 (number)sales_quantity
: 日あたりの売上個数 (number)date
: 売上日 (date)- 購入者管理テーブル
id
: ユニーク ID (string)count
: 日あたりの購入者数 (number)date
: 売上日 (date)「もふもふ」の具体的な実践方法(SQL クエリの出力例)
「もふもふ」は SQL クエリを出力することですので、以下に具体的な調査目的ごとの SQL クエリ例を示します。これらの SQL クエリは、ソースに明示的に記載されているものではなく、各用語の定義とテーブル構造に基づいて私が作成したものです。
1. 「もげ」の発生状況を確認する SQL クエリ
このクエリは、「もげ」の定義(売上が 1,000,000 を下回る日が月の半分以上存在すること)に合致する月があるかを商品売上テーブルから確認します。
-- もげの発生状況を調査するSQLクエリ(商品売上テーブルを使用)
-- 月ごとの売上が1,000,000を下回る日の割合を計算し、月の半分以上を下回る月を特定します
SELECT
strftime('%Y-%m', date) AS sales_month, -- 売上月の抽出
COUNT(CASE WHEN sales < 1000000 THEN 1 END) AS days_below_1M, -- 売上が1,000,000を下回る日数
COUNT(*) AS total_days_in_month, -- 月の総日数
CAST(COUNT(CASE WHEN sales < 1000000 THEN 1 END) AS REAL) / COUNT(*) AS ratio_below_1M -- 売上が1,000,000を下回る日の割合
FROM
商品売上テーブル
GROUP BY
sales_month
HAVING
ratio_below_1M > 0.5; -- 割合が月の半分(0.5)を超える月を抽出
2. 「ふげ」(購入者数の減少)の傾向を調査する SQL クエリ
このクエリは、購入者管理テーブルから日ごとの購入者数の推移を確認し、「ふげ」の兆候(購入者の減少)がないかを調査します。
-- ふげ(購入者数の減少)の傾向を調査するSQLクエリ(購入者管理テーブルを使用)
-- 日ごとの購入者数の推移を確認します
SELECT
date,
count AS purchaser_count -- 日あたりの購入者数
FROM
購入者管理テーブル
ORDER BY
date; -- 日付順に並べ替え
3. 「もげ」と「ふげ」の関連性を調査する SQL クエリ
このクエリは、商品売上テーブルと購入者管理テーブルを結合し、売上の減少と購入者数の減少が同時に発生しているかを確認することで、「もげ」の原因が「ふげ」である可能性を調査します。
-- もげとふげの関連性を調査するSQLクエリ
-- 商品売上テーブルと購入者管理テーブルを結合し、売上と購入者数の両方の推移を確認します
SELECT
pst.date,
pst.sales, -- 日あたりの売上
pst.sales_quantity, -- 日あたりの売上個数
kmt.count AS purchaser_count -- 日あたりの購入者数
FROM
商品売上テーブル AS pst
JOIN
購入者管理テーブル AS kmt
ON
pst.date = kmt.date -- 売上日と購入者管理日の日付で結合
ORDER BY
pst.date; -- 日付順に並べ替え
これらの SQL クエリを出力し、データベースで実行することで、「もふもふ」を実践し、「もげ」の発生状況やその原因が「ふげ」であるかどうかの調査を進めることができます。
上記の通り、もげがふげかもふもふしてくれました。
そしてここで大事なことを忘れていました。
この企業では月末は食品の売り上げが通常の2倍程度に増加する 「ボナペティ祭り」 が実施されます。これをルールに組み込まなければなりません。
下記の文言をルールの Docs に追記します。
* ボナペティ祭り
- 毎月の月末に行われる安売りキャンペーンのこと。食品の売上が通常の2倍程度になるため、もげの基準も2倍になる
そしてNotebookLM側で同期を行い、最新のDocsを読み込んでやります。
その上で下記のように再度投げます。
ボナペティ祭りを考慮して再度もふもふしてください
「ボナペティ祭りを考慮して再度もふもふしてください」というご依頼ですね。承知いたしました。
これまでの会話とソースに基づき、「もふもふ」の意味と、「ボナペティ祭り」を考慮した具体的な SQL クエリ(もふもふ)を提示します。<中略>
-- ボナペティ祭りを考慮した「もげ」の発生状況を調査するSQLクエリ
-- 月ごとの売上が調整された基準(通常時は1,000,000円、ボナペティ祭り期間は2,000,000円)を下回る日の割合を計算し、
-- 月の半分以上を下回る月を特定します。
SELECT
strftime('%Y-%m', sales_data.date) AS sales_month, -- 売上月の抽出
COUNT(CASE
WHEN sales_data.date >= DATE(strftime('%Y-%m', sales_data.date) || '-01', '+1 month', '-3 day') -- 月末3日間をボナペティ祭り期間と仮定
THEN CASE WHEN sales_data.sales < 2000000 THEN 1 END -- ボナペティ祭り期間中は基準が2倍 (1,000,000 * 2 = 2,000,000)
ELSE CASE WHEN sales_data.sales < 1000000 THEN 1 END -- 通常期間は基準が1,000,000
END) AS days_below_adjusted_threshold, -- 調整された基準を下回る日数
COUNT(*) AS total_days_in_month, -- 月の総日数
CAST(COUNT(CASE
WHEN sales_data.date >= DATE(strftime('%Y-%m', sales_data.date) || '-01', '+1 month', '-3 day')
THEN CASE WHEN sales_data.sales < 2000000 THEN 1 END
ELSE CASE WHEN sales_data.sales < 1000000 THEN 1 END
END) AS REAL) / COUNT(*) AS ratio_below_adjusted_threshold -- 調整された基準を下回る日の割合
FROM
商品売上テーブル AS sales_data
GROUP BY
sales_month
HAVING
ratio_below_adjusted_threshold > 0.5; -- 割合が月の半分(0.5)を超える月を抽出
チャット上での文脈を維持したまま、リアルタイムに新しい概念を追加したうえで回答を得ることができました。
また、次回以降は特に事前に用語の説明をすることもなく、ボナペティ祭りなどを考慮した上でもふもふすることが可能になっています。
Github Copilot & 自前の MCP Server を連携させる
上記は NotebookLM にコードやクエリを出力させる方法でしたが、利用者がCopilotなどを使って開発できる環境ではコードの自動生成や編集を行う場合はIDE上から直接行えた方が便利です。
ここではIDE(VS Code) + Copilot Chat + 自前のMCP Server の組み合わせで同じことをやってみます。
なお、copilot-instructions.md でも似たようなことはできますが、複数のリポジトリで同一のドメイン情報を与えたいような場合にはこちらの方法の方が便利かと思われます。
独自ドメイン情報を提供する MCP Server をローカルで立てる
MCP Serverの構築には公式のSDKを使います。
雑に作ったものが下記の通りです。
このMCP Serverはknowledge
ディレクトリ配下に置かれたファイルをconfig.json
の設定の設定に基づいてtoolsとして公開するというものです。
knowledge以下には以下のファイルが格納されています。
[
{
"name": "rule",
"description": "開発用語やルールの詳細。開発時の参照用。",
"filename": "rule.txt",
"format": "text"
}
]
## 用語
- ねこ時間
- 平日の12:00〜13:00のこと
- マタタビショット
- マタタビ200[mg]をねこに投与すること
- 茶トラ制約
- 茶色のトラ猫には100[mg]しかマタタビを与えてはならないという制約
これをcloneしてbuildしてやれば準備は完了です。
IDE 上で MCP Server と連携する
VSCodeであればsetting.jsonに以下のような形でMCP Serverを登録します。
{
"mcp": {
"inputs": [],
"servers": {
"exp-mcp-server": {
"command": "/path/to/node",
"args": [
"/path/to/exp-mcp-server/dist/index.js"
]
}
}
}
}
ext-mcp-serverの上に小さく起動
というリンクが表示されるので、これを押下します。
表示が✓実行中になればOKです。
NotebookLMで指定したのと同様にコードの作成をCopilot Chat上でAgentに指示します。
うまくいきましたね。
既存のコードに対しても同様にドメイン知識を用いて修正が可能です。
今後の課題
AIに与える情報をいかに最新に保つか、という点が挙げられます。
プロダクションコードや成果物から最新の仕様を読み取ることができれば良いのですが、現状ではまだまだ簡単にはいきません。
そうなると手動で定期的に更新ということになりますが、作業コストに加えて精度や網羅性などの問題もあります。
おわりに
NotebookLM や MCP などの技術を用いて、ドメイン特有の知識をAIに与える方法について説明しました。
紹介したような手法は単に実装をはじめとした開発者の工数を削減するだけでなく、業務全般の改善に寄与する可能性があります。
今後も色々試行錯誤しつつ試していきたいと思います。
Discussion