注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。

カスタムモデルを使用してセマンティック検索ワークフローを作成する

このチュートリアルは、Palantir が提供していない埋め込みモデルを使用している方々向けです。Palantir 提供のモデルのリストとPalantir 提供のモデルセマンティック検索チュートリアルをご覧ください。

このページでは、プロンプトを与えられたときに関連するドキュメントを取得できる概念的なエンドツーエンドのドキュメント検索サービスを作成するプロセスを説明しています。このサービスでは、Foundry のモデリング目標を使用してドキュメントを埋め込み、その特徴をベクトルに抽出します。これらのドキュメントと埋め込みは、ベクトルプロパティを持つオブジェクトタイプに格納されます。

この例では、まず Foundry でモデルを設定し、埋め込みを生成するパイプラインを作成します。次に、新しいオブジェクトタイプとそれを自然言語でクエリするための関数を作成します。

私たちは、現在パースされたドキュメントとメタデータ(Document_ContentLinkなど)があるデータセットから始めます。次に、Document_Contentから埋め込みを生成し、それらをセマンティック検索でクエリできるようにします。

埋め込みを生成するためのデータセット

KNN 機能の詳細については、Foundry のドキュメンテーションのKNN Functions on Objectsセクションを参照してください。

値の置換

このワークフロー全体で、選択した値を置き換えることができます。ただし、各インスタンスで一貫性を保つことが必要です。例えば、ObjectApiNameの各インスタンスは常にDocumentで置き換えられます。

置き換える必要がある値は以下の通りです:

  • ObjectApiName:唯一の ObjectType の識別子、今回の場合は Document注意: 識別子は場合によっては最初の文字が小文字の objectApiName として表示されることがあります。
  • ModelApiName:唯一のモデルの識別子。
  • OutputDatasetRid埋め込み変換からの出力データセットの識別子。
  • InputDatasetRid埋め込み変換の入力データセットの識別子。
  • ModelRid埋め込み変換Live Modeling Deployment の作成で使用されるモデルの識別子。

1. Foundry でモデルを使用して埋め込みを作成する

Foundry のモデルから埋め込みを作成するためのいくつかのオプションがあります。この例では、私たちはインポートされたオープンソースモデルと対話するための変換を作成します。私たちは all-MiniLM-L6-v2 モデルを使用します。これは一般的なテキスト埋め込みモデルで、次元(サイズ)384のベクトルを作成します。このモデルは、Foundry オントロジーの vector タイプと互換性のあるベクトルを出力する他の既存のモデルと交換することができます。新しいオープンソースモデルをインポートするには、私たちの言語モデルのドキュメンテーションを参照してください。

この例では、私たちはこのモデルを取り、変換を実行して埋め込みを生成し、必要な後処理を行います。今回の場合、私たちはデータをモデルを通して embedding を返し、embedding 値(ダブル配列)をベクトル埋め込みに必要な型に合わせるために float にキャストします。

いくつかの点を考慮すべきです:

  • schema 変数の各 StructField は、処理された入力データセット(InputDatasetRid)に存在する行に関連し、モデルによって追加される embedding 行です。
  • データを大規模に扱う場合、Pandas データフレームが過度に大きいと変換が失敗する可能性があります。これらのケースでは、変換は Spark で実行する必要があります。
  • Graphics Processing Units(GPU)は、変換によって埋め込みが生成される速度を向上させるために利用することができます。GPU を使用するには、変換に @configure デコレータを追加します。この機能を環境で有効にするには、Palantir の担当者に連絡してください。

以下に例の変換を示します:

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from transforms.api import configure, transform, Input, Output from palantir_models.transforms import ModelInput from pyspark.sql.functions import pandas_udf, PandasUDFType from pyspark.sql.types import StructType, StructField, IntegerType, StringType, FloatType, ArrayType import numpy as np @configure(profile=["DRIVER_GPU_ENABLED"]) # GPUが有効になっている環境でこの行を削除してください @transform( dataset_out=Output("OutputDatasetRid"), dataset_in=Input("InputDatasetRid"), embedding_model=ModelInput("ModelRid") ) def compute(ctx, dataset_out, dataset_in, embedding_model): # モデルの入力列と一致させる spark_df = dataset_in.dataframe().withColumnRenamed("Document_Content", "text") def embed_df(df): # エンベッディングを作成 output_df = embedding_model.transform(df).output_data # floatの配列にキャスト output_df["embedding"] = output_df["embedding"].apply(lambda x: np.array(x).astype(float).tolist()) # 不要な列を削除 return output_df.drop('inference_device', axis=1) # 更新されたスキーマ schema = StructType([ StructField("UID", IntegerType(), True), StructField("Category", StringType(), True), StructField("text", StringType(), True), StructField("Link", StringType(), True), StructField("embedding", ArrayType(FloatType()), True) ]) udf = pandas_udf(embed_df, returnType=schema, functionType=PandasUDFType.GROUPED_MAP) output_df = spark_df.groupBy('UID').apply(udf) # 出力DataFrameを書き込む dataset_out.write_dataframe(output_df)

次に、ユーザーのクエリに基づいて埋め込みを作成し、既存のベクトルと検索できるようにするために、Live Modeling Deploymentが必要です。この部分で使用するモデルは、現在のステップで最初の埋め込みを生成するために使用されたものと同じものである必要があります。

2. オブジェクトタイプの作成

ここまでで、最初のステップでのバッチモデリングデプロイメントを使用して生成された浮動小数点ベクトルの埋め込みを含む列がある新しいデータセットができているはずです。次に、オブジェクトタイプを作成します。

オブジェクトタイプの名前は Document とし、embedding プロパティはプロパティタイプ Vector に設定します。これには、以下の2つの値を設定する必要があります。

  1. 次元: これは、列 embedding で生成される配列の長さです。
  2. 類似度関数: これは、異なるオブジェクトからの2つの embedding 値間の距離が計算される方法です。

新しいベクトルプロパティタイプ

このオブジェクトタイプが作成されると、Documentation オブジェクトを意味的に検索するために使用できるプロパティ(embedding)ができます。

ObjectApiName の値は、オブジェクトタイプが保存された後に利用可能になり、作成されたオブジェクトタイプの設定ページで見つけることができます。これについての詳細は、ドキュメントの オブジェクトタイプの作成 セクションで確認できます。

3. Live Modeling Deployment の作成

オブジェクトに埋め込みがプロパティとして存在するようになったので、ユーザーのクエリに対して低レイテンシで埋め込みを生成する必要があります。これらの埋め込みは、類似の埋め込み値を持つオブジェクトを見つけるために使用されます。これを行うには、Functionsで高速で低レイテンシのアクセスが可能なライブモデルデプロイメントを作成します。

ライブモデリングデプロイメントの設定方法や、モデリングセクションの よくある質問 を確認してください。

Live Deployment API Name で設定する値は、上記で言及された代替値 ModelApiName に相当します。

4. モデル上のFunctionsで埋め込みを作成する

Functionsでベクトルプロパティを有効にする

進む前に、Functionsのコードリポジトリ内の functions.json ファイルに、"enableVectorProperties": true"useDeploymentApiNames": true の両方のエントリが存在していることを確認してください。これらのエントリが存在しない場合は、functions.json に追加して変更をコミットし、続行してください。さらなる支援が必要な場合は、Palantir担当者にお問い合わせください。

最後のステップは、このオブジェクトタイプをクエリするための関数を作成することです。検索フェーズでは、全体的な目標は、ユーザーの入力を受け取り、先に作成したライブモデリングデプロイメントを使用してベクトルを生成し、オブジェクトタイプ上でKNN検索を行うことができるようにすることです。 このユースケースのサンプル関数は以下に示されており、それらが配置されるべきファイル構造も含まれています。

ベクトルプロパティへの編集は、アクションとFunctionsによって適用できます。

詳細は、モデル上のFunctionsのドキュメントを参照してください。

ファイル構造

|-- functions-typescript
|   |-- src
|   |   |-- tests
|   |   |   |-- index.ts  // テストのインデックスファイル
|   |   |-- semanticSearch.ts  // セマンティック検索のファイル
|   |   |-- service.ts  // サービスロジックのファイル
|   |   |-- index.ts  // エントリーポイントのインデックスファイル
|   |   |-- tsconfig.js  // TypeScriptの設定ファイル
|   |   |-- types.ts  // 型定義のファイル
|   |-- functions.json  // Functionsの設定ファイル
|   |-- jest.config.js  // Jestの設定ファイル
|   |-- package-lock.json  // 依存関係の固定ファイル
|   |-- package.json  // プロジェクトの設定ファイル
|-- version.properties  // バージョン情報のプロパティファイル

functions-typescript/src/types.ts

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // @foundry/functions-api から Double をインポート import { Double } from "@foundry/functions-api"; // IEmbeddingModel インターフェースの定義 export interface IEmbeddingModel { // 埋め込み関数の定義:入力は string、出力は IEmbeddingResponse の Promise embed: (content: string) => Promise<IEmbeddingResponse>; } // IEmbeddingResponse インターフェースの定義 export interface IEmbeddingResponse { text: string // テキスト embedding: Double[] // 埋め込みベクトル inference_device?: string // 推論デバイス(オプション) } // IEmbeddingRequest インターフェースの定義 export interface IEmbeddingRequest { text: string // テキスト }

functions-typescript/src/index.ts

Copied!
1 2 // 日本語のコメント: セマンティック検索からSuggestedDocsをエクスポートします export { SuggestedDocs } from "./semanticSearch";

functions-typescript/src/service.ts

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { ModelApiName } from "@foundry/models-api/deployments"; import { IEmbeddingRequest, IEmbeddingResponse } from "./types"; // モデルにアクセスするためのサービス export class EmbeddingService { // コンテンツを埋め込む非同期メソッド public async embed(content: string): Promise<IEmbeddingResponse> { // リクエストの作成 const request: IEmbeddingRequest = { "text": content, }; // ModelApiName.transformを使用してリクエストを処理し、結果を返す return await ModelApiName.transform([request]) .then((output: any) => output[0]) as IEmbeddingResponse; } }

functions-typescript/src/semanticSearch.ts

Copied!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import { Function, Integer, Double } from "@foundry/functions-api"; import { Objects, ObjectApiName } from "@foundry/ontology-api"; import { EmbeddingService } from "./service"; import { IEmbeddingResponse, IEmbeddingModel } from './types'; // 類似文書を提案するクラス export class SuggestedDocs { embeddingService: IEmbeddingModel = new EmbeddingService; // 類似文書を取得する関数 @Function() public async fetchSuggestedDocuments(userQuery: string, kValue: Integer, category: string): Promise<ObjectApiName[]> { const embedding: IEmbeddingResponse = await this.embeddingService.embed(userQuery); const vector: Double[] = embedding.embedding; return Objects.search() .objectApiName() .filter(obj => obj.category.exactMatch(category)) .nearestNeighbors(obj => obj.embedding.near(vector, {kValue: kValue})) .orderByRelevance() .take(kValue); } /** * 以下は、fetchSuggestedDocumentsの代替手段で、類似度のしきい値を適用します。 * それ以外の場合、kValueの数だけ文書が常に返されるため、類似しているかどうかに関係なく。 * 距離関数の計算は、埋め込みプロパティに定義された距離関数に依存します。 * ここでは、コサイン類似度が、埋め込みモデルが正規化されたベクトルを生成する場合には、単純なベクトルドット * 積で計算できると仮定しています。 */ @Function() public async fetchSuggestedDocumentsWithThreshold(userQuery: string, kValue: Integer, category: string, thresholdSimilarity: Double): Promise<ObjectApiName[]> { const embedding: IEmbeddingResponse = await this.embeddingService.embed(userQuery); const vector: Double[] = embedding.embedding; return Objects.search() .objectApiName() .filter(obj => obj.category.exactMatch(category)) .nearestNeighbors(obj => obj.embedding.near(vector, {kValue: kValue})) .orderByRelevance() .take(kValue) .filter(obj => SuggestedDocs.dotProduct(vector, obj.embedding! as number[]) >= thresholdSimilarity); } // ベクトルのドット積を計算する関数 private static dotProduct<K extends number>(arr1: K[], arr2: K[]): number { if (arr1.length !== arr2.length) { throw EvalError("二つのベクトルは同じ次元でなければなりません"); } return arr1.map((_, i) => arr1[i] * arr2[i]).reduce((m, n) => m + n); } }

5. 関数を公開し、例で使用する

ここまでで、自然言語でオブジェクトを検索するセマンティック検索を実行できる関数ができました。最後のステップは、関数を公開することと、ワークフローで使用することです。ドキュメント検索の例をさらに構築していくために、この関数をテキスト入力で呼び出し、上位2件のマッチングするドキュメント記事をユーザーに返すWorkshopアプリケーションを作成します。

例のドキュメントサービスでセマンティック検索を作成するプロセスは以下の通りです。

  1. Workshopアプリケーションを作成することから始めます。
  2. テキスト入力文字列セレクタを追加します。文字列セレクタは、ドキュメントカテゴリーを選択してフィルター処理するために使用されます。テキスト入力と文字列セレクタの両方が、公開されたKNNドキュメントフェッチ関数への入力として機能します。
  3. 最後に、関数と選択した入力から生成された入力オブジェクトセットを持つオブジェクトリストウィジェットを追加します。以下のように表示されます。

KNN関数でオブジェクトセットを生成

ここから、入力は、オブジェクトタイプ内のドキュメントをセマンティック検索し、最も関連性のある2つのドキュメントを返すために使用されます。これは、ベクター プロパティとセマンティック検索の簡単なユースケースの1つです。以下のスクリーンショットで、結果として得られるWorkshopアプリケーションの例を確認してください。

例:セマンティック検索ワークショップ