(この記事は心のこもった手書きでお届けしております)
@kuといいます。
10月に放送された「ガイアの夜明け」で紹介された、タクシーに乗りたいお客さまからの電話を受けて指定場所に迎車するLLMプロダクトmaido(Mobility AI Dispatch Operator)の開発に携わっています。 この記事ではmaidoで行っているLangfuseとDSPyを使ってプロンプトを定量的に改善していく仕組みをご紹介します。
Langfuseとは
Langfuseは、LLMアプリケーションのobservabilityを確保するツールです。電話ごとのセッションや、トレース単位でLLM呼び出しの入出力、エラー、レイテンシー、コストを記録することが可能です。それに加えてプロンプト管理、評価用のデータセット管理、評価の実行も可能です。 もともとは、maidoでデプロイなしでプロンプトを更新するためのツールを探していて、求めている機能がカバーされていてかつ使いやすいという点で選びました。そういうミニマムな用途でもおすすめです。
伝統的なシステムでは、新しいコードをデプロイするとき、様々なテストでアプリケーションが壊れていないことを確認します。同様にLLMアプリケーションでもプロンプトを更新したとき、これまで期待する出力が得られていたケースで失敗するようになっていないのを確認したいです。そのためには、伝統的なテストケースと同様にプロンプトを評価するための入力と期待する出力のペアが必要です。
LangfuseにはDatasetsという機能がありLangfuse内に記録されたtraceをもとにリアルなデータセットを容易に作ることができます。
DSPy
DSPyは、自動的にプロンプトの改善を行うためのツールです。 データセットをもとにプロンプトの評価を行い、出力が間違っていたときになぜ間違ったのか、入力とプロンプトから理由を考えてプロンプトに反映して再評価、という人間がやっているようなことをやってくれます。これを並列かつ大規模に繰り返し行ってプロンプトを改善していきます。
人間の仕事はデータセットに正解データを追加するのみになり、機械の心に響くプロンプトの執筆に時間をかけずにプロンプトが改善されるループを作ることできます。

探索的にプロンプトを生成して、データセットで良し悪しを評価する仕組みなので、LLMの呼び出し回数は多くなります。
そのためどれくらいコストがかかるのか気になるところですが、これもLangfuseで管理することができます。OpenTelemetry経由で管理する方法がDSPy - Observability & Tracing で紹介されているので、これを参考に追加しました。(最近イベントで教えていただきました)
maidoでの実例
実際にmaidoでどのように活用しているか、実例をご紹介します。 maidoは、お客さまからの電話を受けて、大きく以下の流れで配車を行います。
- 用件の確認
- 迎車地の確認
- newmoのマッチングシステムに配車依頼を投入する
各ステップはいくつかのエージェントに分けて実装してあります。
用件の確認
タクシー会社の電話には、様々な電話がかかってきます。迎車の依頼のほかにも、忘れ物の確認、予約や料金のお問い合わせなど様々です。 現在maidoは車種の指定のない即時の配車のみ対応しているため、電話の冒頭で今からすぐの配車を希望されているかを確認しています。用件が即時の配車でないときは、電話を担当者に転送しています。
迎車地の確認
即時の配車であることを確認した後、希望の迎車地の場所や住所を教えてもらいます。大きな建物の場合、正面だけでなく裏側にも出入り口があることがあるので、どこに車を停めるかも教えてもらいます。エージェントは地理情報サービスAPIを使用して指定された場所が実在することを確認しつつ、最終的には迎車地を緯度経度で決定します。
会話内容の再確認
迎車地が決まったら、newmoのマッチングシステムに配車依頼を投入...したいところですが、ここで投入前にお客さまとの会話を以下の点で再確認しています。
- 迎車日時の指定がないか
- 乗客が5人以上でないか
- 2台以上の依頼でないか
- ペットと同乗しないか
これらの確認は、LLMの性質と人間の性質からくる、難しさ、不正確さへの対策として行っています。
LLMの性質
LLMはプロンプトの内容を常に守ってくれるわけではありません。「迎車地を聞く」ことをエージェントに指示したとき、付随する細かいルールとして記述された内容には注意が向かず、聞き流してしまうことがあります。 実際の会話の中だと、場所を聞くタイミングで「玄関前に明日の8:30に」という形で日時を指定されることがよくあります。このときLLMはお客さまは予約を希望していることに気が付かずそのまま配車しようとすることがあります。(ちゃんと予約だと気づいて、プロンプトに記述されているとおりオペレータに転送することもあります)
人間の性質
自分も含めて、人間は会話するときに高い集中力を持って真剣に厳密な受け答えをするわけではありません。うまく聞き取れないときも、流れを止めずにそのまま話を進めたりします。また、システムが意図する「今すぐの配車」の概念をお客さまに簡潔に正確に伝えるのは難しいです。結果として冒頭に「今からすぐ」と言われるお客さまでも「玄関前に30分後に」のような形で時間を指定されることはよくあります。
このような2者の性質を踏まえて、会話内容と配車しようとしている内容に齟齬がないか、対応できない依頼を受けようとしていないかをレビューしています。配車エージェント全体のガードレールとも言えるかもしれません。
このレビューを行うプロンプトをLangfuseに作ったデータセットとDSPyで改善しています。
データセット例
トラブルになってしまったケースをもとに以下をdatasetに保存しています。
- 会話内容
- 内容に対して期待される出力のペア
この作業はLangfuse上からGUIからも、APIで自動的に追加することもできます。現状では追加した後にexpected_outputの部分だけ手で修正しています。
{ "input": [ {"role": "model", "content": "お迎えに上がる住所または場所を教えてください"}, {"role": "user", "content": "靭公園"}, {"role": "model", "content": "かしこまりました、靭公園のどちらにおつけしましょう"}, {"role": "user", "content": "四つ橋筋のとこに10時で"}, ], "expected_output": { "immediate_dispatch": false, "one_car": true, "four_or_less_passengers": true, "with_pet": false } }
(本物は冗長なので簡略化しています)
実際に生成されたプロンプト
こうして作ったデータセットとDSPyが生成した、即時の配車かどうかを判定するプロンプトの一部を以下に示します。
もとのプロンプトには「予約ではなく今からすぐの即時の配車だったらtrueを返す」程度のことだけを書いたところから、学習に使用していない未知のテストデータセットを100%正しく判定できるプロンプトが生成されました。 人間の目から見ると冗長、やや不正確な部分もありますが、待っているだけで得られたプロンプトとしては十分な品質です。
3. 判定ルール(詳細)キーワード・表現リスト
条件 判定 即時配車 caller が「今すぐ」「すぐに」「即時」などを言及し、かつ
① 時刻・日時の具体的表現(「10時半」「明日午後3時」等)が無い、
② dispatcher が「予約」と答えていない。予約配車 caller が ① 具体的な時刻・日時を言及(例: 「10時半」「明日午後3時」など)、
② 未来の時間を示す表現(「午後」「明日」「来週」など)がある、
③ 「今すぐ」と同時に未来の時間を言及した場合は、未来の時間が優先。dispatcher が予約を示唆した caller が「今すぐ」と言ったが、dispatcher が「予約」や「日程を決めたい」と返答した場合は dispatcher の指示に従い false。曖昧・情報不足 時刻・日時の表現が全くない、または「今すぐ」に対して明確な反論や予約の提示がない場合、デフォルトは true。ただし、何らかの時刻情報(数字+「時」「分 」など)が含まれていればfalseと判断。重要な注意点
- 即時: 「今すぐ」「すぐに」「即時」など。
- 予約・日程: 「10時半」「明日午後3時」「来週金曜」など。
- 日時の形: 「午前」「午後」「時間」「時刻」「分」など。
- 場所表現: 「駅前」「ホテルのロビー」等は判定に影響しない。
- 未来の時刻が同時に言及された場合は必ず予約と判断。 例:
caller: 今すぐで、10時半に来てほしいです。→ 予約。- dispatcher が「すぐに手配いたします」と言っても、caller の予約情報が優先。 例:
caller: 6時40分に迎えに来てください→ 予約。- 呼び出し方の表現が曖昧でも「今すぐ」の明示がある場合は即時。 例:
caller: 車呼んで欲しいんです→ 即時。
DSPyはドキュメントも充実しており、今回紹介している改善は GEPA for Structured Information Extraction for Enterprise Tasks をそのまま使うことができました。
最後に
今回ご紹介したケースは、出力が2値で表現されるため評価が容易でした。自然言語が出力されるものや、複数ターンや複数エージェントを経ての出力は評価自体が難しいこともあります。
しかし、うまく評価しやすい単位に分解できれば、評価→改善ループを構築して半自動的に品質を改善できるようになります。失敗したケースも、今後失敗しないようにするための貴重なケースとして前向きに捉えられるようになります。
伝統的なシステムでは、インシデントに繋がったバグを修正するとき、テストケースとして再現コードを追加し、新しい実装がバグを解決したことを確認したりします。同様に、LLMアプリケーションでも失敗したケースを蓄積し、評価に組み込むことで定量的に品質を上げていくことができます。
定量的な評価ができる状態を作り、実際の結果をデータセットとして蓄積し、LLMアプリケーションが確実によくなっていくループを作りましょう。
免責事項
Langfuseは求める機能がコンパクトにまとまっていてわかりやすく、価格的にも使いはじめやすいのでおすすめです。帽子をもらったのと関係なくおすすめします。
@langfuse capいただいたのでこれから布教していきたいと思います pic.twitter.com/LP2EdbQmew
— ku (@ku) 2025年11月18日