注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。
このチュートリアルは、Palantir が提供していない埋め込みモデルを使用している方々向けです。Palantir 提供のモデルのリストとPalantir 提供のモデルセマンティック検索チュートリアルをご覧ください。
このページでは、プロンプトを与えられたときに関連するドキュメントを取得できる概念的なエンドツーエンドのドキュメント検索サービスを作成するプロセスを説明しています。このサービスでは、Foundry のモデリング目標を使用してドキュメントを埋め込み、その特徴をベクトルに抽出します。これらのドキュメントと埋め込みは、ベクトルプロパティを持つオブジェクトタイプに格納されます。
この例では、まず Foundry でモデルを設定し、埋め込みを生成するパイプラインを作成します。次に、新しいオブジェクトタイプとそれを自然言語でクエリするための関数を作成します。
私たちは、現在パースされたドキュメントとメタデータ(Document_Content
やLink
など)があるデータセットから始めます。次に、Document_Content
から埋め込みを生成し、それらをセマンティック検索でクエリできるようにします。
KNN 機能の詳細については、Foundry のドキュメンテーションのKNN Functions on Objectsセクションを参照してください。
このワークフロー全体で、選択した値を置き換えることができます。ただし、各インスタンスで一貫性を保つことが必要です。例えば、ObjectApiName
の各インスタンスは常にDocument
で置き換えられます。
置き換える必要がある値は以下の通りです:
ObjectApiName
:唯一の ObjectType の識別子、今回の場合は Document
。注意: 識別子は場合によっては最初の文字が小文字の objectApiName
として表示されることがあります。ModelApiName
:唯一のモデルの識別子。OutputDatasetRid
:埋め込み変換からの出力データセットの識別子。InputDatasetRid
:埋め込み変換の入力データセットの識別子。ModelRid
:埋め込み変換とLive Modeling Deployment の作成で使用されるモデルの識別子。Foundry のモデルから埋め込みを作成するためのいくつかのオプションがあります。この例では、私たちはインポートされたオープンソースモデルと対話するための変換を作成します。私たちは all-MiniLM-L6-v2
モデルを使用します。これは一般的なテキスト埋め込みモデルで、次元(サイズ)384のベクトルを作成します。このモデルは、Foundry オントロジーの vector
タイプと互換性のあるベクトルを出力する他の既存のモデルと交換することができます。新しいオープンソースモデルをインポートするには、私たちの言語モデルのドキュメンテーションを参照してください。
この例では、私たちはこのモデルを取り、変換を実行して埋め込みを生成し、必要な後処理を行います。今回の場合、私たちはデータをモデルを通して embedding
を返し、embedding
値(ダブル配列)をベクトル埋め込みに必要な型に合わせるために float にキャストします。
いくつかの点を考慮すべきです:
schema
変数の各 StructField
は、処理された入力データセット(InputDatasetRid
)に存在する行に関連し、モデルによって追加される embedding
行です。@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が必要です。この部分で使用するモデルは、現在のステップで最初の埋め込みを生成するために使用されたものと同じものである必要があります。
ここまでで、最初のステップでのバッチモデリングデプロイメントを使用して生成された浮動小数点ベクトルの埋め込みを含む列がある新しいデータセットができているはずです。次に、オブジェクトタイプを作成します。
オブジェクトタイプの名前は Document
とし、embedding
プロパティはプロパティタイプ Vector
に設定します。これには、以下の2つの値を設定する必要があります。
embedding
で生成される配列の長さです。embedding
値間の距離が計算される方法です。このオブジェクトタイプが作成されると、Documentation
オブジェクトを意味的に検索するために使用できるプロパティ(embedding
)ができます。
ObjectApiName
の値は、オブジェクトタイプが保存された後に利用可能になり、作成されたオブジェクトタイプの設定ページで見つけることができます。これについての詳細は、ドキュメントの オブジェクトタイプの作成 セクションで確認できます。
オブジェクトに埋め込みがプロパティとして存在するようになったので、ユーザーのクエリに対して低レイテンシで埋め込みを生成する必要があります。これらの埋め込みは、類似の埋め込み値を持つオブジェクトを見つけるために使用されます。これを行うには、Functionsで高速で低レイテンシのアクセスが可能なライブモデルデプロイメントを作成します。
ライブモデリングデプロイメントの設定方法や、モデリングセクションの よくある質問 を確認してください。
Live Deployment API Name で設定する値は、上記で言及された代替値 ModelApiName
に相当します。
進む前に、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 // バージョン情報のプロパティファイル
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 // テキスト }
Copied!1 2
// 日本語のコメント: セマンティック検索からSuggestedDocsをエクスポートします export { SuggestedDocs } from "./semanticSearch";
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; } }
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); } }
ここまでで、自然言語でオブジェクトを検索するセマンティック検索を実行できる関数ができました。最後のステップは、関数を公開することと、ワークフローで使用することです。ドキュメント検索の例をさらに構築していくために、この関数をテキスト入力で呼び出し、上位2件のマッチングするドキュメント記事をユーザーに返すWorkshopアプリケーションを作成します。
例のドキュメントサービスでセマンティック検索を作成するプロセスは以下の通りです。
ここから、入力は、オブジェクトタイプ内のドキュメントをセマンティック検索し、最も関連性のある2つのドキュメントを返すために使用されます。これは、ベクター プロパティとセマンティック検索の簡単なユースケースの1つです。以下のスクリーンショットで、結果として得られるWorkshopアプリケーションの例を確認してください。