Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
MCPを使ったRAGをPHPで作る
Search
uiui
August 09, 2025
0
12
MCPを使ったRAGをPHPで作る
MCPを介してRAGをどう実現するのか調査した時のお話。
既存のRAGシステムをMCPを使ってAgentシステムへ昇華できるか模索中です。
uiui
August 09, 2025
Tweet
Share
More Decks by uiui
See All by uiui
レガシーシステムから学ぶ エラー処理との付き合い方
uiokuyama
1
120
Featured
See All Featured
BBQ
matthewcrist
89
9.8k
Build The Right Thing And Hit Your Dates
maggiecrowley
37
2.8k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.6k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
7
830
KATA
mclloyd
32
14k
Balancing Empowerment & Direction
lara
3
600
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3k
Six Lessons from altMBA
skipperchong
28
4k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
4 Signs Your Business is Dying
shpigford
184
22k
The Invisible Side of Design
smashingmag
301
51k
Building an army of robots
kneath
306
46k
Transcript
MCPを使ったRAGをPHPで作る 2025-07-30 PHP勉強会 @uiui
エンジニア歴4年目のPHPerです 自己紹介 uiui X: @U_Okuyama_Devlp work:最近はAWS/Laravel/Vue あたりでやっています。 また生成AI系をWebアプリの機能に組み込むみたいなのが 多いです。
それ以外:元々ヴァイオリン弾きです。最近は日曜音楽家と名乗ることにしました。 あと辛い物が好き!
時代はAgent ・RAGシステムをさらに色々なものに連携できるようになるんじゃないか …? ・より自動化効率化できるなにかがあるかも ・将来アプリのインターフェースはどう変わってくのか ・将来私たちは何を作っていくんだろう 色んな妄想がふくらむ … …とりあえず何か作って知ることから始めます
今日のゴールは Github Copilot Chat (Agent) / VSCode で質問したら PHPアプリDB内のテキストデータをベクトル検索し
(ベクトル検索は構築済み) 取得した情報をもとに回答できるようにする! *認証・エラーハンドリングなどは全部省きます!
つまりMCPを使ったRAGを作ります そうすればそのあとにアクションを連結したりとか色々な使い方ができるはず。
(・Postgresql + pg_vector) (・今回はLaravel) ・VScode / Github Copilot Chat (Agent)
使用するもの
ベクトル検索について簡単に
ベクトル検索までのフロー データ保存時: ①テキストデータをembedding→ベクトル型のカラムに保存 データ検索時: ②検索クエリ文書をベクトル化 →②と①カラムの値で距離計算 (類似度)&並び替えをしてデータを取得
Postgres + pg_vectorで ベクトル型を保存・検索できる Column | Type |
Collation | Nullable | Default -------------+--------------------------------+-----------+----------+----------------------------------------------- embeddings | vector(1536) | | not null | ベクトルは’[1,2,3]’のような感じで表される select * from items order by embeddings <=> '$vector’ limit 20
Embedding手段 ・AWS BedrockでEmbedding(API風に) ・OpenAIのAPI
ベクトル化について超大雑把にいうと 単語や文章の意味を機械がわかるように 数値化(ベクトル化 )したもの
問題: ?に当てはまる言葉はなんでしょう King - Man + Woman = ? 1.
Uncle 2. Queen 3. Grandmother *古典的だけど
これを機械が解くイメージをしてみる *注:このグラフはテキトーなイメージです! 各言葉を高貴さと性別の2次元だけでグラフに表してみよう a King - Man + Woman
= ? なんかできそう
a このグラフでは高貴さと性別の2項目でしか判断できないが 実際には1000次元以上を扱う 1000項目くらいなら方向が近い=意味も近い のイメージもわく気がする ベクトルの方向が近いほど類似している = 意味の近さを計算で算出できる
・実際にはDLによってこのベクトルを算出するモデルは学習されるわけなので、各 項目が人間の認識できる項目なわけではない 1000次元以上のベクトルに、単語や文章の意味を埋め込んで特徴を表現している ↓ ベクトル化はEmbeddingと言われる API等も「Embedding」で検索🔍
MCPについて
MCPとは AIと外部データやツールの接続を標準化したもの うーん、わかりにくい
ToolsとAgent ・すでに2023年後半くらいからToolsというものがあった /** * 現在の天気を取得する * @param string $location
日本語の文字列で場所を示す * @return string */ public function getCurrentWeather(string $location): string; /** * 現在の気温を取得する * @param string $location * @param string $format * @return string */ public static function getCurrentTemperature(string $location, string $format="celsius"): string; LLM あなたはこの関数を 使って情報を 取得できます! 私達
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 使える関数 getCurrentWeather(string $location) :string getCurrentTemperature(string $location,
…)
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 使える関数 getCurrentWeather(string $location) :string getCurrentTemperature(string $location,
…) 今日の気候に合わせた 服装を提案しよう
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 使える関数 getCurrentWeather(string $location) :string getCurrentTemperature(string $location,
…) 今日の気候に合わせた 服装を提案しよう getCurrentWeather(“osaka”); を実行したい!
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 使える関数 getCurrentWeather(string $location) :string getCurrentTemperature(string $location,
…) 今日の気候に合わせた 服装を提案しよう getCurrentWeather(“osaka”); を実行したい! “晴れのち雨”
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 使える関数 getCurrentWeather(string $location) :string getCurrentTemperature(string $location,
…) 今日の気候に合わせた 服装を提案しよう getCurrentTemperature(“osaka”); を実行したい!
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 使える関数 getCurrentWeather(string $location) :string getCurrentTemperature(string $location,
…) 今日の気候に合わせた 服装を提案しよう getCurrentTemperature(“osaka”); を実行したい! “32.3”
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 今日の気候に合わせた 服装を提案しよう 天気は晴れのち雨 気温は32.3°Cだから
ToolsとAgent 今日は大阪に行くんだけど 何着て行けばいいかな? 今日の気候に合わせた 服装を提案しよう 天気は晴れのち雨 気温は32.3°Cだから 午前は暑いから半袖+薄いパンツで OK。日傘
か帽子あると安心。午後は雨っぽいから折りた たみ傘と薄いパーカー持ってって!靴は濡れて もいいやつが◎
データ取得だけじゃなくて 何かを実行みたいにも使える Toolだけ用意すればなんでもできるじゃん!
データ取得だけじゃなくて 何かを実行みたいにも使える Toolだけ用意すればなんでもできるじゃん! →世間を賑わせている Agent
Toolだけ、?
google driveから データ取得したい メールの下書き 作りたい タスクを自動で 立ててほしい 社内Qiitaから 情報取得したい 色んなAPI
色んなAPI さながらAPI連携のよう 作成しなきゃいけない Toolが多すぎる …
って思ってた人が世界にたくさんいたはず…
MCPの誕生!嬉しい! ツール等を使う AI側(MCPクライアント )と、 ツール等を提供する (MCPサーバ)側 との間の標準プロトコル ! 加えて、構築済みのオープンソース
MCPサーバも公開される https://github.com/modelcontextprotocol/servers 好きなAIアプリケーションに、好きなデータソースを接続できる。
MCPサーバが提供できるものは3つ ・Tools 実行できる関数 ・Resources 添付できるデータ ・Prompts 提供できるプロンプトテンプレート
自前のMCPサーバを実装するには ・通信方法にStreamable HTTPというのがある クライアントが単一のHTTP POSTでJSON=RPC2.0リクエストを送信してくる サーバーは通常のJSONレスポンスか、SSEで返信する つまり、単一URLに対してjson込みのPOSTリクエストが来るので 仕様に沿って適切なjsonを返せばOK! 思ったよりずっと簡単!
受信するjsonの構造 { jsonrpc: "2.0"; id: string | number; method:
string; params?: { [key: string]: unknown; }; } methodでどういう種類のリクエストか判断する
リクエストの種類(method) ・initialize 初期化(どんな機能が使えますか?バージョンは? ) ・notifications/initialized 初期化しましたイベント ・tools/list 使えるツール一覧を取得したいです ・tools/call
ツールを呼び出したいです などなど
さっそく作っていきます
HTTPエンドポイントパスは以下とします http://localhost:8080/mcp Route::post('/mcp', [McpController::class, 'mcp']); route
使えるツール ・getDocument(string $query) システムのドキュメントからベクトル検索を行い、情報を取得する
まずは初期化から
MCPの初期化 Client Server 初期化(使える機能リスト・Version) こんな機能使えます [xxx,xxx] 初期化OKです initialize request
initialize response notifications/initialized request
Controller initializeに対応 class McpController extends Controller { public function mcp(Request
$request) { $data = match($request->input('method')) { 'initialize' => $this->initialize(), }; return response()->json([ 'jsonrpc' => '2.0', 'id' => $request->input('id'), 'result' => $data, ]); } private function initialize(): array
private function initialize(): array { return [ "protocolVersion" => "2024-11-05",
"capabilities" => [ "tools" => [ "listChanged" => true ], ], "serverInfo" => [ "name" => "XX System Document Server", "version" => "1.0.0" ], ["instructions" => "このサーバーはXXシステムのドキュメントから、文章クエリでドキュメントを検索 し取得することができます。"], ]; } Controller initializeに対応
private function initialize(): array { return [ "protocolVersion" => "2024-11-05",
"capabilities" => [ "tools" => [ "listChanged" => true ], ], "serverInfo" => [ "name" => "XX System Document Server", "version" => "1.0.0" ], ["instructions" => "このサーバーはXXシステムのドキュメントから、文章クエリでドキュメントを検索 し取得することができます。"], ]; } 使える機能リスト Controller initializeに対応
notifications/initializedに対応 class McpController extends Controller { public function
mcp(Request $request) { $data = match($request->input('method')) { 'initialize' => $this->initialize(), 'notifications/initialized' => http_response_code(202) && exit(), }; return response()->json([ 'jsonrpc' => '2.0', 'id' => $request->input('id'), 'result' => $data, ]); } レスポンスは何でもよい
notifications/cancelledでエラー出ないように class McpController extends Controller { public function
mcp(Request $request) { $data = match($request->input('method')) { .. 'notifications/cancelled' => http_response_code(202) && exit(), }; return response()->json([ 'jsonrpc' => '2.0', 'id' => $request->input('id'), 'result' => $data, ]); } ちゃんと実装するときは、リソースを無駄遣いしないように処理を中断したい!
ツール一覧の提供
Controller tools/listに対応 $data = match($request->input('method')) { 'initialize' => $this->initialize(), 'notifications/initialized'
=> http_response_code(202) && exit(), 'tools/list' => $this->toolsList(), }; return response()->json([ 'jsonrpc' => '2.0', 'id' => $request->input('id'), 'result' => $data, ]);
private function toolsList(): array { return [ 'tools' => [
[ "name" => "getDocument", "title" => "Xシステムのドキュメントから情報を取得", "description" => "クエリ文章から関連するドキュメントを取得します。", "inputSchema" => [ "type" => "object", "properties" => [ "query" => [ "type" => "string", "description" => "ドキュメントを検索する文章クエリとして渡してください。", ] ], "required" => ["query"] ] ], ] ]; }
ツールの呼び出しと情報を返す
Controller tools/callに対応 $data = match($request->input('method')) { ... 'tools/call' => $this->getDocument($request->input('params.arguments.query')),
}; return response()->json([ 'jsonrpc' => '2.0', 'id' => $request->input('id'), 'result' => $data, ]);
Controller tools/call private function getDocument(?string $query): array { $documents =
$this->searcher->searchDocuments($query); return [ 'content' => [ ['type' => 'text', 'text' => $documents->toText()] ], 'isError' => false, ]; } 検索したドキュメントの内容をテキストで返す。複数ドキュメントの場合は、わかりや すい区切り文字を入れるなど、適宜対応
補足:searchDocuments() //クエリをembeddingAPIに投げてベクトル /** @var Vector $vector **/ $vector =
$this->embedding->getEmbeddings($query); DB::select( "select * from documents order by embeddings <=> '{$vector->__toString()}' limit 5" );
Github Copilot Chat (Agent) / VSCodeで接続する
コマンドパレットからMCPサーバーの追加 http://localhost:8080/mcp を登録
ツールが追加される 🎊
質問をすると... 入力: 生成した文章クエリ 出力: 検索にマッチした情報
が返ってきた! *MCPツールを使ってくれるようにプロンプトを整えるか、 ツールのdescription等を整える必要あり
おまけ
精度を改善する①
問題①:検索に引っかかるようなクエリ文章を渡し てくれない ・曖昧な質問内容 ・ドメイン特有の言葉を正しく使って質問してくれない ・そもそもLLMが、どういうクエリ文章を作成すればいいのかわかってくれない チャットの部分以外の文脈をちゃんと渡してあげよう
・Tool 実行できる関数 ・Resources 添付できるデータ ・Prompt 提供できるプロンプトテンプレート MCPサーバから プロンプトテンプレートを提供できる
prompts/listとprompts/getの追加 $data = match($request->input('method')) { .. 'prompts/list' => $this->promptsList(),
'prompts/get' => $this->getPrompt($request->input('params.arguments.question')), 'completion/complete' => $this->retCompletion(), };
使えるプロンプト一覧を提供 private function promptsList(): array { return [ 'prompts'
=> [ [ "name" => "generateSearchQueryText", "title" => "ドキュメントを検索するためのクエリ文章を生成", "description" => "ドキュメントを検索するためのクエリ文章を生成します。", "arguments" => [ [ "name" => "question", "description" => "システムに対する質問文", "required" => true ] ] ], ] ]; }
system, user のプロンプトを書く $system = “文脈が伝わるようなプロンプトを与える ”; $human =
<<<EOT ###質問 {$question} ###指示 システムのどんな機能を使いたいのか、もしくはどんな仕様を知りたいのか を文章で出力した後、 getDocumentツールを使って XXシステムのドキュメントを検索して、 その内容から読み取り、クライアントからの 問い合わせに適切な回答をしてください。 必ずドキュメントに記載されている情報を使ってください。 EOT; 質問を代入できる箇所を作る
プロンプトを提供する private function getPrompt(string $question): array { $system =
"..."; $human = "..."; return [ 'description' => 'XXシステムのドキュメントから情報を 取得するためのクエリ文章を生成し、 XXシステムにRAG検索を行います。 ', 'messages' => [ [ 'role' => 'system', 'content' => [ 'type' => 'text', 'text' => $system ] ], [ 'role' => 'user', 'content' => [ 'type' => 'text', 'text' => $human ] ] ], ]; }
VSCodeでプロンプトを使ってみる 代入する文を入力
プロンプトが入力される 良いクエリ文章を生成してくれるようになった ✨ RAGでいうクエリ拡張の処理を Agentの方で補完する感じ
(精度を改善する②サーバ側)
問題②:体感より違う文章が似てる判定される (ベクトル検索の問題) ・一定の文章量をベクトル化して検索対象にしている場合、文章の塊全体の 意味が埋め込まれたベクトルになる。 マッチしてほしいドキュメントがランキング的に下位になる場合がある 対策:関連度順に並べるのに特化したAIモデルでフィルターする ベクトル検索で粗くフィルター リランキングモデルでさらにフィルター
ご清聴ありがとうございました!