SCIM検索の限界

大規模なOkta orgでユーザーを検索するのは、それほど簡単ではありません。ロサンゼルスを拠点にする営業担当のすべての契約社員を見つけたい場合、SCIMの構文を正確に記述しないと、期待どおりの結果は得られません。たとえば、プロファイル属性でユーザーを検索するには、次のようになります。

profile.department eq "営業" and profile.city eq "ロサンゼルス" and profile.userType eq "契約社員"

この構文は、多くのユーザーにとって難しいものです。その理由には以下が挙げられます。

  • 特定の構文を知らなければいけない。
  • 検索はキーワードに基づいて行われるため、「私の直属の部下」のようなクエリでは、現在の管理者の配下にあるユーザーリストは見つからない。Okta管理コンソールはマネージャーIDでのフィルタリングをサポートしているものの、通常の検索では「私の直属の部下」が管理者のIDに等しいprofile.managerIdに変換されるべきことを認識しない。
  • わずかなタイプミスが、ゼロまたは予期しない結果になる可能性がある。 

最近の社内ハッカソンで、私たちはあるシンプルな問いに答えてみようとしました。もし、Okta管理者が平易な言葉遣いで意図することを入力するだけで、SCIMの詳細をシステムが処理してくれるとしたら、どうなるでしょうか?つまり、以下のようなことです。

代替する構文

使用する表現

search=profile.department eq "営業" and profile.title eq "マネージャー"

営業マネージャーと営業ディレクターを探してください

profile.managerId eq "00u1ab2c3d4e"

私の直属のすべての部下を表示してください

この問いに答えるために、プロトタイプを構築しました。自然言語を解釈し、それをOktaのリソースにマッピングし、管理者がSCIM構文を使用しなくても関連するユーザーとアプリケーションを返す、AIを活用したセマンティック検索エンジンです。この記事の残りの部分では、プロトタイプをどのように組み立てたか、そして将来の本番環境に対応できる実装に向けたビジョンを説明します。

ワークフローの概要

Okta管理者がグローバル検索バーに自然言語のクエリを入力すると、Okta JavaバックエンドはAPI呼び出し(/api/internal/admin/searchおよび/api/v1/internal/apps)を傍受します。標準的なSCIMクエリをすぐに実行する代わりに、ユーザーの未加工のクエリを使ってPython AIサービスにAPI呼び出しを行います。

Pythonサービスは、多段階検索パイプラインを実行し、関連性の高いユーザーおよびアプリIDのランク付けされたリストを返します。JavaバックエンドはこれらのIDを取得し、既存のOktaデータレイヤーから完全なリソースオブジェクトを取得して、UIに表示する最終的なJSON応答を構築します。 

Python AIサービスアーキテクチャ

データレイヤー:このプロトタイプでは、サービスはオフラインのインデックス処理を使用します。デモOkta orgからユーザーとアプリケーションを取得し、これらのリソースをローカルJSONスナップショットに変換し、ダウンストリーム埋め込みのためにテキスト表現を作成します。

AIインテリジェンスレイヤー:

  • 埋め込みサービス:AWS Bedrockの埋め込みモデル(たとえばCohere Embed、Amazon Titan)を使用して、各テキストドキュメントをベクトルにマッピングします。これらのベクトルは、FAISS(Facebook AI類似度検索ライブラリ)インデックスに保存されます。
  • 自然言語処理:Anthropic Claude 4.5のようなLLMを使用してクエリを解釈し、SCIMフィルターを生成します。
  • クエリ理解:小規模なヒューリスティックサービスが、構造化された属性(部署、都市、managerIdなど)を自然言語クエリから抽出し、検索結果を絞り込みます。

高度な検索パイプライン:

  • クエリ処理:受信クエリは分析され、同義語で拡張され、ベクトルに変換されます。
  • 候補の検索:FAISSインデックスから最も近いベクトルを検索し、一致する各ベクトルにハイブリッドスコアを割り当てます。
  • 結果の絞り込み:最後に、上位候補をCohere Rerankモデルに送信して並べ替え、最終的なリソースIDリストを返します。

このパイプラインでFAISSベクトルインデックスを使用することで、検索範囲を数千のユーザーとアプリから、少数の候補セットに絞り込みます。これにより、最終的なRerankモデルに渡されるコンテキストのサイズが削減され、運用コストとレイテンシーが削減されます。

データセットの安全な構築

PIIデータに触れないようにデモのOkta orgを使用し、このorgにリアルなデータを取り込むために、ユーザーとアプリケーションのランダムなプロファイルを生成しました。すべてのデータは、カスタムのPythonスクリプトで作成され、ローカルJSONファイルにエクスポートされました。これにより、スナップショットに対してシステムをオフラインで実行することができました。

Oktaオブジェクトを検索可能なテキストに変換

未加工のユーザーおよびアプリケーションのプロファイルだけでは、優れたセマンティック検索には不十分です。また、完全なコンテキストと意図も把握する必要があります。オフラインインデックスエンジンは、ベクトルが作成される前に、このデータ準備を処理します。Oktaオブジェクトの場合、重要なテキストフィールドは最初に1つのテキストドキュメントに結合され、解釈する単一の説明が埋め込みモデルに確実に提供されるようにします。

これらのドキュメントは、データ内の既知の制限に対処することで強化されます。ユーザーの場合、一般的な属性の同義語拡張を含むドキュメントを作成することになり、アプリケーションの場合、アプリのプロパティに基づいてヒューリスティックに生成されたキーワードを含めることになります(アプリがConcurの場合、「経費」を追加するなど)。

Amazon Bedrockの埋め込みレイヤー

Cohereの埋め込みレイヤーには再試行ロジックが含まれており、特定のシナリオでTitanモデルにフォールバックします。その結果生成されるベクトルは、ドキュメントの意味を数値形式でエンコードしたもので、FAISSインデックス検索で使用するために、保存前に単位長に正規化されます。

ハイブリッドランキング

このハイブリッド検索手法は、ベクトル検索によるセマンティックシグナルと、従来の属性マッチングを組み合わせたものです。クエリごとに、ヒューリスティッククエリ理解のステップで、ユーザーが提供したテキストからヒント(部署や都市など)を抽出します。候補の検索段階では、FAISSインデックスに対して最も類似したベクトルがクエリされます。次に、ベクトルの類似性と抽出された属性の存在を組み合わせたハイブリッドスコアが結果に割り当てられます。最終的に、上位候補はCohere Rerankモデルに渡され、最終的な並べ替えが行われます。

LLMとイベントフックを活用した将来の拡張機能

このハッカソンのプロトタイプは、データの静的スナップショット上で実行されましたが、本番環境に対応できるシステムにするには、リアルタイムデータとライブクエリを使用したハイブリッド検索方法という2つの機能強化が必要です。

インデックスを最新の状態に保つために、定期的なスナップショットをOktaイベントフックを使用したイベント駆動型アーキテクチャに置き換えます。サーバーレス機能は、Okta syslogイベントをリッスンし、ベクトルデータベースを更新します。これにより、検索インデックスはライブデータから数秒遅れるだけになります。

次に、LLM(GPTやClaudeなど)を使用してユーザーのクエリを解釈し、正確なSCIMフィルターを生成します。これはその後、ライブのOkta APIに対して直接実行されます。これにより、常に最新で正確な結果が得られます。 

おわりに

Oktaセマンティック検索は、「管理者がSCIMフィルターを手動で記述する必要がなくなったらどうなるか?」というシンプルな問いから始まりました。AIパイプライン(ベクトルインデックス作成、LLM、ハイブリッドスコアリング)を組み合わせることで、管理者が平易な言葉でOktaリソースを検索できる実用的なプロトタイプを構築しました。

AIシステムを構築して実際のアイデンティティの問題を解決することに興味のある開発者の方は、Oktaの採用情報ページでキャリアの機会を探してみてください。または、独自のプロジェクトを始めてみましょう。

アイデンティティ施策を推進