📚

YOJOのLLMフローエンジニアリング・アーキテクチャを解説します

2024/08/08に公開

こんにちはPharmaXの上野(@ueeeeniki)です。

今回はPharmaXが手掛けるYOJOというサービスで実践しているLLMのフローエンジニアリングのアーキテクチャを詳しく解説したいと思います。

昨日開催したイベントでも、フローエンジニアリングについてご紹介しました。

https://yojo.connpass.com/event/325426/

https://speakerdeck.com/pharma_x_tech/flow-engineering-in-multi-agent-llm-chat-application-03dd5337-87fd-4fa1-b4de-96cf9f50e001

イベント内のディスカッションでも、フローエンジニアリングという概念は非常に有用であるにも関わらず、まだあまり認知を得られていないという話になったので、今後はガンガン布教して行きたいと思います!

今回例に取り上げるLLMアプリケーションの概要

フローエンジニアリングがイメージしやすくなるように、 YOJOというサービスの例を簡単にご説明します。

PharmaXのYOJOというサービスは、LINEで薬剤師に相談をして医薬品を購入できるというtoCサービスです。

PharmaX内には、ユーザーである患者さんからの質問にチャットで答える薬剤師が多数在籍しています。
PharmaXは薬局にシステムを販売するのではなく、直接患者さんに医薬品を販売するオンライン薬局であり、薬剤師の方々はあくまでPharmaXに所属する薬剤師です。

詳細は省きますが、メインで扱っているのは、処方箋が必要な処方薬ではなく、処方箋が不要なOTC医薬品です。

今回例として取り上げるLLM機能は、薬剤師がチャットするメッセージ内容をサジェストします。
エンジニアにとってのcopilotのような役割だと思っていただければよいでしょう。

下記が薬剤師メンバーが使うチャット画面です。
(ローカル環境での私自身との会話画面なのでセンシティブな情報は含みませんが、画面の一部をマスクしていることをご了承ください。)

患者さん(ユーザー)からメッセージを受信したタイミングでLLMによる返信のサジェストが作られます。
サジェストされた内容を薬剤師が確認し、必要があれば修正して送信します。


薬剤師が患者さんの状態を確認しつつ、チャットするための管理画面

詳細は省きますが、現在では、LINE登録〜購入までの流れのかなりの割合が薬剤師の修正なしで、承認するだけで送信することができています。

フローエンジニアリングとはなにか

フローエンジニアリングとは、あるタスクを1つのLLMエージェントですべて解かせるのではなく、そのタスクを細分化して、エージェントやアプリケーションの実装を組み合わせてどう解いていくかをデザインすることを指します。

私の理解では、タスクの分岐によっては、LLMに解かせるのを諦めて人が解くパターンが含まれるものもフローエンジニアリングと呼びます。
例えば、カウンセリングAIを作ろうとしているとしたら、普段はLLMエージェントが対応しつつ、自殺願望を仄めかすような発言をした場合には、人間が対応するモードに切り替わるというようなイメージです。

このようにエージェントの組み合わせ全体デザインし、目的とする処理系を作り上げることをフローエンジニアリングと呼ぶと理解しています。

例えば、社内のバックオフィス系の質問に答えるチャットボットを作成することを考えると、1つのプロンプトに分類とメッセージ作成をタスクをぶち込むアーキテクチャを図示すると下記のようになります。


1エージェントにすべてのタスクを任せる例

一方、下記のように質問内容を分岐して、その分岐結果に応じて次の質問回答をするプロンプトを呼び分けるアーキテクチャを取ることもできます。

タスクごとにエージェントを分けてフローエンジニアリングする例。この例では4エージェントある。

これがフローエンジニアリングの例です。

PharmaXでは、フローエンジニアリングという言葉が注目される前から、実質的にフローエンジニアリングを行っていましたが、
アプリケーションの実装コストが高いので、なかなか一般には受け入れられないだろうなと思っていました。

ですが、最近は、DifyやBedrock Studioのようなほぼノーコードなツールや、PromptFlowのようなローコードツールも人気を博し、徐々にフローエンジニアリングを実現しやすくなってきた印象です。

フローエンジニアリングのメリット

フローエンジニアリングの基本思想は、小さなエージェントを組み合わせることだと紹介しました。

単一の目的を上手くこなす小さなエージェントを組み合わせることで、処理系全体で精度を向上させることができることが一番重要なメリットです。

また、エージェントのタスクを単一にすることでプロンプトの肥大を避け、保守性を向上させることもできます。
下記の記事でもエージェント設計の基本原則として「エージェントがこなすタスクはできる限り小さく単一にする」を挙げています。

https://zenn.dev/pharmax/articles/ae19bafbcfeb23#エージェントがこなすタスクはできる限り小さく単一にする

詳しくはこちらの記事も合わせてお読みただければと思います。

YOJOのフローエンジニアリング・アーキテクチャの概要

では、YOJOのメッセージ提案機能におけるフローエンジニアリングの概要をご説明します。

YOJOのメッセージ提案機能は、

  1. ルールベースでLLM処理可能かを判定
  2. LLMで会話を分類しLLM処理可能かを判定
  3. LLMで次のフェーズに移るべきかどうかを判定
  4. LLMでメッセージを作成
  5. LLMで作成されたメッセージを評価(LLM as a Judge)し、一定の水準を下回ったら再生成して、クリアしたもののみをサジェストする

という順番で動きます。


メッセージが作成されるまでの一連の処理イメージ

ただメッセージを生成させるだけで、かなり複雑な手順を踏んでいるのがお分かりいただけるかと思います。

会話分類の結果によっては、LLMでのメッセージ生成を諦めて、最初から人が対応するパターンもあります。
これは、危険性を鑑みてということでもありますが、(リソース不足や優先順位の問題で)精度高く出力するプロンプトをまだ作れていないだけという場合もあります。

次のセクションで詳しく解説しますが、YOJOでは、フェーズという考え方で患者対応の段階を捉えていて、フェーズごとに動く処理のパターンやプロンプトを切替えています。
そのため、今このタイミングでフェーズを切り替えるかべきか?というのを判断するLLMエージェントも動いています。

このように目的とする処理を完了するまでに、ルールベースやエージェントの出力結果によってその後の処理を切替えたり、呼び出すプロンプトが変わったするのがフローエンジニアリングです。

YOJOにおけるフェーズという考え方

上記でも述べましたが、YOJOでは、フェーズという考え方で患者対応の段階を捉えていて、フェーズごとに動く処理のパターンやプロンプトを切替えています。

特に新規購入までの流れをフェーズに分割し、各フェーズで同様のフローエンジニアリングを定義しています。

下記の図のようにそれぞれのフェーズで会話分類・フェーズ切替判定・メッセージ提案・評価のエージェントが動きます。

さすがにこれ以上の詳細に立ち入るのは控えておきますが、複数のフェーズで使いまわしているプロンプトもあれば、同じようなタスクでも異なるプロンプトを使っている場合もあります。

フローエンジニアリングの課題

一方で、フローエンジニアリングにも課題はあります。
主要な課題と解決策について紹介したいと思います。

  • 実装コストが高い
  • 処理のトレーシング難易度が高い
  • 処理に時間がかかってしまう
  • プロンプトの数が増えて管理コストが増大する

実装コストが高い

フローエンジニアリングという概念があまり広がらない要因として、実装コストが高いことがあげられると思っています。
Difyのようなツールが出てきたとしても、そもそもフローを設計する難易度が下がるわけではありません。

正直な感想としては、エンジニアリングができる方ではないと設計するのは難しいのではないかと思います。
エージェントを分割する単位を考えるのは、ソフトウェアエンジニアリングでいうところのクラス分割の考え方に構造的には近しいと感じます。

LLMマルチエージェントのフローエンジニアリング実践ガイド』の中でもエージェント分割の考え方には触れていますが、現時点でベストプラクティスというレベルまで言語化できているわけではありません。

私たちは、エージェント間にできる限り依存関係がないように設計しようとしてます。
各エージェントが自分のタスクに集中していれば、エージェントの集合としては、解きたい複雑なタスクが解けているという設計が理想です。

このあたりがまさにクラス分割の考え方に近しいものを感じます。

処理のトレーシング難易度が高い

フローエンジニアリングは、処理が多段に実行されるので、結局どこで処理が終わったのかなどを後から確認できる事が重要です。

そして、これが私たちがLangSmithを採用している一番の理由です。

LangSmithはフローエンジニアリングの一連の処理をまとめて可視化することができます。

上図の例は、会話分類でLLM処理の流れが止まってしまったケースです。
このように処理がどこまで行って止まったのかが一目瞭然です。

LangSmithを採用した理由は詳しくは下記の記事をご覧ください。
https://zenn.dev/pharmax/articles/21478167d4d4c4

処理に時間がかかってしまう

フローエンジニアリングでは、最終的な出力までに複数の処理が行われるため、処理系全体のレスポンス速度は遅くなってしまう傾向にあります。

この問題を解決する方法は別の記事で詳しく解説したいと思いますが、簡単に言ってしまえばできる限り並列処理を行うしかないでしょう。

LangGraphを使えば、並列処理するグラフ構造も簡単に実装することが可能です。

https://zenn.dev/pharmax/articles/78f2e6a51a459e

ですが、ユーザーが出力を待っているような、文字通りのリアルタイム性を求められるようなサービスなら、1秒から数秒以内にレスポンスを返さなければ、使い物になりません。

あまりに複雑なフローを組んでしまっては、1秒などの短い時間で返すのはどう工夫しても難しくなってしまうかもしれません。

どうしても難しい場合は、発想を変えて、フローエンジニアリングの処理速度を向上させるという手段だけではなく、UXで処理時間を稼ぐことを考えてみると良いかもしれません。

出力の時間を稼ぐためにいわゆるフィラーのようなテクニックを駆使するという方法もあります。
「うーん」や「ええっと」、「なるほど」みたいなやつです。
あるいは、とりあえず相手が言ったことをオウム返しすることで時間を稼ぐという方法もあるかもしれません。
「〇〇にお悩みなのですね。」「〇〇ということ、大変お辛いですね。」のようなイメージですね。

プロンプトの数が増えて管理コストが増大する

フローエンジニアリングを行っていると、プロンプトの数がどうしても多くなってしまいます。

YOJOでは、すでに40個以上のプロンプト・テンプレートを運用していました。
今の計画では、2024年内に60個は超えると考えています。

このようにプロンプトの数が増えていくとどうしても管理コストは上がっていきます。

LLMの実験管理・評価ツールをLangSmithに移行した理由を語ります』という記事の中でも触れていますが、特にLangSmithはプロンプト管理のための機能が弱いこともあり、管理コストが増大しています。

この課題の解決策は正直今のところ見つかっていません。

LLMマルチエージェントのフローエンジニアリング実践ガイド』の中の、「LLMの知能向上によってマルチエージェントの構成は不要になるか」というセクションでも解説していますが、
LLMの知能が向上すれば、エージェントに複数のタスクを解かせることができるようになるとは思います。
つまり、すでにLLM化している機能では、エージェント数を減らすことはできるとは思います。

ですが、今後もLLM機能の数自体を増やそうとしていますし、プロンプト数の増大を根本的に解決できるということにはならなさそうです。

まとめ

今回はYOJOのフローエンジニアリング・アーキテクチャについて詳しく解説しました。

フローエンジニアリングは、LLM時代のアプリケーション開発に取って重要な考え方になると思っているので、今後も布教して行きたいと思います。

フローエンジニアリングは、設計から実装までアプリケーションエンジニア的な思考が重要になるでしょう。
また、LLMアプリケーションのUXを向上させるには、あらゆる手段を組み合わせた総合格闘技になっていくと思います。

PharmaXでは、ソフトウェアの実装力と設計力を武器に最高のUXを実現するLLMアプリケーションを一緒に作ってくれるエンジニアを募集しています。

もし少しでも気になった方がいらっしゃれば、是非カジュアルにお話できれば嬉しいです。

https://x.com/ueeeeniki

PharmaXテックブログ

Discussion