SAP AI CoreのGenerative AI Hubに2024Q4、新たに 「Grounding(グラウンディング)」 機能が追加されました。これは生成AIの回答を企業固有の知識やデータに紐づけて信頼性を高める仕組みです。一般にグラウンディングとは、モデルの出力を検証可能な情報源に結び付けるプロセスを指し、これにより生成AIの回答を特定のデータに基づいたものにでき、いわゆるハルシネーションを起こりにくくします 。本記事では、ハンズオンコンテンツを通しながらSAP BTP 上のSAP AI CoreによるGrounding機能の全体像を技術者向けに解説し、その目的や仕組みについて俯瞰して解説します。
SAP AI CoreのGenerative AI Hubに追加されたGrounding機能は、一言でいうと生成AIと企業の専門データをつなぐための機能です。大規模言語モデル(LLM)の汎用的な知識に、企業内の最新かつ正確なデータを組み合わせることで、回答の精度向上と意思決定支援を図る目的があります 。例えば、これまでの生成AIはインターネット上に存在し、学習に利用された一般的な知識をもとに回答を生成しますが、Grounding機能を利用すると社内の文書や知識データベースから関連情報を検索・参照して回答に反映できます。この専門データの検索と文脈情報の活用によって、生成AIの出力内容に現実的な裏付け(根拠)を与え、業務で使える信頼性の高い回答を得ることが可能になります。
Generative AI Hubは、SAP AI Core / SAP AI Launchpad上で提供される生成AI統合環境であり、様々なLLMへのアクセスやAIワークフローのオーケストレーション機能を備えています。Grounding機能はこのHub内の「オーケストレーションモジュール」に組み込まれた新機能です。オーケストレーションモジュールでは、プロンプトの処理やモデル実行の流れに沿って複数のステップを定義できますが、Groundingはその中で外部データ取り込みや、取り込んだデータからの検索を担うステップとして位置付けられます。つまり、Generative AI Hub上でLLMに質問を投げる際、まずGroundingステップで関連する社内データを検索し、その結果をもとにLLMが回答を生成する、という流れになります。
引用:SAP® Business AI:2024年第4四半期リリースハイライト
なお、SAP AI Launchpad上からは 「Grounding Management」 と呼ばれる管理画面にもアクセスが可能であり、ここでGrounding用の知識ベース設定を行えるようになっています。例えば、どのデータソース(後述のSharePointやSAP HANA Cloudなど)をGroundingに利用するかを設定し、インデックス作成の状況をモニタリングするといった操作が可能です。このようにGrounding機能は、Generative AI Hub内のオーケストレーションフローの一部でありながら、他の機能やUIとも連携して統合的に管理・活用できるよう設計されています。
Grounding機能を実現する背後には、SAP HANA Cloud Vector Engineにより提供されるベクトルデータベースを用いた高度な検索技術が使われています。大まかな仕組みは、「ドキュメント埋め込み(Embedding)+ベクトル検索+生成AI」 というRetrieval Augmented Generation (RAG) のパターンで利用される場合があります。具体的には次のように動作します。
前者では、SAP AI Coreが用意するデータパイプラインを実行すると、指定したリポジトリから文書を収集して自動でテキスト分割・ベクトル変換し、ベクトルデータベース(SAP HANA Cloud)に保存が可能です。後者では、開発者自らが文章を一定長に分割しAPI経由でベクトルデータベースに登録します。いずれの場合も、一度知識ベースとなるベクトルデータが作成されれば、それ以降はLLM問い合わせ時に高速な類似検索が可能になります。
現在、パイプライン機能を利用してGroundingで参照できる文書管理サービス・外部サービスとして、Microsoft Sharepoint / Amazon S3 / SFTPサーバー が公開されています。それぞれpdfとdocxファイルタイプをサポートしており、コンテンツの更新は日次で行われます。詳細は参考文献に記載のSAP Help Portalをご参照ください。
Grounding機能により、これまで難しかった 「社内ナレッジを活用した高度なAIアシスタント」 が実現しやすくなります。以下に代表的なユースケースの例を挙げます。
以上のように、Grounding機能は社内情報に基づいた高度なQAやコンテンツ生成全般に威力を発揮します。特に社内ポータルや従業員向けアシスタント、顧客対応チャットボット、経営レポート自動化など、「大量のテキストやデータを迅速に活用したい」シナリオで有用です。これにより従業員の情報アクセスが容易になり、日常の意思決定スピードが向上することが期待できます。Groundingは今後、様々な業種・業務のAIユースケースで鍵となる技術になるでしょう。
本ブログでは、Amazon S3をGenerative AI HubのGrounding機能に対してオンボーディングし、Retrieval APIからデータを取得するところまでを扱います。オーケストレーションモジュールを使ってRAGエンドポイントを作成する部分に関しては、別の機会に公開する資料をご参照ください。
P.S. ブログを公開しました。
「SAP AI Core Orchestration機能 を解き明かす」
今回のハンズオンはSAP Build Code上にて実施します。Devスペースにログインしてターミナルを立ち上げ、まずはサンプルコードをクローンしましょう。
git clone https://github.com/watwatwhat/SAPAICore_Grounding_sample.git今回のハンズオンの前提条件として、Object Store on SAP BTP (Standartプラン)のインスタンスと、SAP AI Core(Extendedプラン)のインスタンスをSAP BTP Cockpitから作成して、SAP AI LaunchpadにSAP AI Coreのインスタンスをオンボーディングしておきます。
まずは、今回のハンズオンで用いるサービス群のサービスキーとユーザー定義文字列を設定しましょう。
クローンしたファイル群の中から、下記の3つのファイルを編集します。
credentials/ai_core_sk.json
credentials/object_store_sk.json
credentials/user_defined_variable.json1,2つ目に関しては、SAP AI Coreのインスタンスに作成したサービスキーのjson、Object Store on SAP BTP のインスタンスに作成したサービスキーのjsonをペーストしてください。3つ目に関しては、任意の値を入力してください。使える文字には制限があるようなので、特にこだわりがなければデフォルトと同じような名前をつけることを推奨します。IDの文字が規定に沿っていないという旨のエラーが後段で出た場合、この辺りを設定し直してください。
まずは環境のセットアップを行いましょう。SAP AI Coreではリソースグループという単位を作成し、その中でクレデンシャルなどの管理を行います。まずはリソースグループの作成を行い、その後にObject Store on SAP BTPのサービスキーを利用して接続のセットアップを行うため、下記のコマンドを実行します。
. ./01_grounding/01_prerequisites/00_init.sh
node ./01_grounding/01_prerequisites/01_prerequisites.jsこの処理により、下記の処理が行われます。
主要なスクリプト部分は下記の通りです。
// リソースグループ作成
async function createResourceGroup(token) {
const url = `${AI_API_HOST}/v2/admin/resourceGroups`;
const payload = {
resourceGroupId,
labels: [
{
key: 'ext.ai.sap.com/document-grounding',
value: 'true',
},
],
};
try {
await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
console.log(' Resource Group created!');
} catch (err) {
if (err.response && err.response.status === 409) {
console.log(' Resource Group already exists');
} else {
throw err;
}
}
}
// S3 Secret 作成
async function createS3Secret(token) {
const url = `${AI_API_HOST}/v2/admin/secrets`;
const payload = {
name: secretName,
data: {
url: Buffer.from(`https://${s3Info.bucketName}.s3.${s3Info.region}.amazonaws.com`).toString('base64'),
authentication: "Tm9BdXRoZW50aWNhdGlvbg==",
description: Buffer.from('Generic secret for accessing S3 bucket for grounding').toString('base64'),
access_key_id: Buffer.from(s3Info.accessKeyId).toString('base64'),
secret_access_key: Buffer.from(s3Info.secretAccessKey).toString('base64'),
bucket: Buffer.from(s3Info.bucketName).toString('base64'),
host: Buffer.from(s3Info.host).toString('base64'),
region: Buffer.from(s3Info.region).toString('base64'),
username: Buffer.from(s3Info.username).toString('base64'),
},
labels: [
{
key: 'ext.ai.sap.com/document-grounding',
value: 'true',
},
{
key: 'ext.ai.sap.com/documentRepositoryType',
value: 'S3',
},
],
};
try {
await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'AI-Resource-Group': resourceGroupId,
},
});
console.log(' S3 Secret created');
} catch (err) {
if (err.response && err.response.status === 409) {
console.log(' S3 Secret already exists');
} else {
throw err;
}
}
}これが完了すると、SAP AI LaunchpadのSAP AI Core Administration -> Generic SecretsにAmazon S3へのアクセス用の認証情報が登録されます。SAP AI Coreのパイプラインは、この認証情報を用いて、Amazon S3のバケットへアクセスします。
次に、SAP AI Coreのパイプライン機能を使って、Amazon S3の中に格納された文書を、ベクトルデータベース上に取り込んで行きます。が、その前にAmazon S3の中に文書を格納しておきましょう。今回は、下記のパスに格納されたpdfをAmazon S3に格納しておきます。
./01_grounding/docs/SAP_BTP_Overview.pdf早速、下記のNodeスクリプトを実行しましょう。
node ./01_grounding/02_pipelineAPI/02_uploadDocs.jsうまく実行できると、署名付きURLが表示されるように構成しています。ここにアクセスすると、誰でも1時間はpdfを読み取ることができます。Amazon S3へのpdf文書の格納が完了したことへの確認としてご利用ください。
このスクリプトでは、下記の通りAWS SDKを利用して、Amazon S3にpdfファイルをアップロードする処理が記述されています。
// AWS S3 クライアントの設定
const s3 = new AWS.S3({
accessKeyId: s3Info.accessKeyId,
secretAccessKey: s3Info.secretAccessKey,
endpoint: s3Info.host, // SAP BTPのObject Storeの場合も有効
region: s3Info.region,
signatureVersion: 'v4',
s3ForcePathStyle: true,
});
// アップロード対象のファイル
const filePath = path.join(__dirname, 'docs/SAP_BTP_Overview.pdf');
const fileContent = fs.readFileSync(filePath);
const fileName = 'SAP_BTP_Overview.pdf';
// アップロードパラメータ
const uploadParams = {
Bucket: s3Info.bucketName,
Key: fileName,
Body: fileContent,
ContentType: 'application/pdf'
};
// アップロードと署名付きURLの生成
s3.upload(uploadParams, (err, data) => {
if (err) {
console.error(' アップロード失敗:', err);
return;
}
console.log(' アップロード成功:', data.Location);
// Presigned URL 発行(1時間有効)
const signedUrl = s3.getSignedUrl('getObject', {
Bucket: s3Info.bucketName,
Key: fileName,
Expires: 3600, // 秒数(例: 3600秒 = 1時間)
});
console.log(' 署名付きURL (1時間有効):');
console.log(signedUrl);
});次に、パイプラインを生成します。次のnodeスクリプトを実行してください。
node ./01_grounding/02_pipelineAPI/03_createPipeline.jsこのスクリプトでは、下記の主要な処理が実行されています。
// Pipeline 作成
async function createS3Pipeline(token) {
const url = `${AI_API_HOST}/v2/lm/document-grounding/pipelines`;
const payload = {
type: "S3",
configuration: {
destination: secretName
}
};
console.log(' payload 内容:', JSON.stringify(payload, null, 2));
try {
const response = await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'AI-Resource-Group': resourceGroupId,
},
});
console.log(' Pipeline 作成成功!');
console.log(' Pipeline ID:', response.data.id);
return response.data;
} catch (err) {
if (err.response) {
console.error(' Pipeline 作成エラー:', {
status: err.response.status,
statusText: err.response.statusText,
data: err.response.data,
});
} else if (err.request) {
console.error(' Pipeline 作成エラー: No response received', err.request);
} else {
console.error(' Pipeline 作成エラー:', err.message);
}
}
}パイプラインの生成がうまくいくと、下記のnodeスクリプトからパイプラインの一覧を確認できます。
node /01_grounding/02_pipelineAPI/04_managePipeline.js listこのスクリプトでは、下記処理が実行されます。
// Pipeline 一覧取得
async function listPipelines(token) {
const url = `${AI_API_HOST}/v2/lm/document-grounding/pipelines`;
const response = await axios.get(url, getRequestOptions(token));
console.log(' Pipeline 一覧:');
console.log(JSON.stringify(response.data, null, 2));
}
// 実行処理
(async () => {
try {
const action = process.argv[2]; // list / get / status / delete
const pipelineId = process.argv[3]; // オプション:get/status/delete用
if (!action || !['list', 'get', 'status', 'delete'].includes(action)) {
console.log(' 使用方法:');
console.log('node 04_managePipeline.js list');
console.log('node 04_managePipeline.js get <pipelineId>');
console.log('node 04_managePipeline.js status <pipelineId>');
console.log('node 04_managePipeline.js delete <pipelineId>');
return;
}
const token = await getXsuaaToken();
switch (action) {
case 'list':
await listPipelines(token);
break;
case 'get':
if (!pipelineId) throw new Error('Pipeline ID が必要です');
await getPipeline(token, pipelineId);
break;
case 'status':
if (!pipelineId) throw new Error('Pipeline ID が必要です');
await getPipelineStatus(token, pipelineId);
break;
case 'delete':
if (!pipelineId) throw new Error('Pipeline ID が必要です');
await deletePipeline(token, pipelineId);
break;
}
} catch (err) {
if (err.response) {
console.error(' エラー:', {
status: err.response.status,
statusText: err.response.statusText,
data: err.response.data,
});
} else {
console.error(' エラー:', err.message);
}
}
})();この状態で、1回目のパイプラインが実行されます。これにより、Amazon S3内に格納された文書は分割・ベクトル化処理され、ベクトルデータベースに格納されます。この結果として、SAP AI Launchpadからはチャンク化されたpdfの内容が確認できるようになります。
これで事前のベクトル化処理は完了です。最後に、Retrieval APIから関連する文書を抽出できることを確認しましょう。下記のコマンドで、関連文書を検索することができます。
node ./01_grounding/03_retrievalAPI/06_searchRetrieval.js "What is BTP?"もしくは、Groundingを行う対象の文書を格納したリポジトリを指定して、下記のように検索を行うこともできます。
node ./01_grounding/03_retrievalAPI/06_searchRetrieval.js "What is BTP?" <リポジトリID>
このnodeスクリプトでは内部的にSAP AI Coreに用意された下記のエンドポイントを呼び出していますが、これをRAGアプリケーションのRetrievalツールの中から呼び出すことで、SAP AI Core – Generative AI HubのGrounding機能を利用したRAGを構成することができます。
const url = `${AI_API_HOST}/v2/lm/document-grounding/retrieval/search`;このスクリプトの全体的な主要な処理は下記の通りです。
// Retrieval Search 呼び出し
async function searchRetrieval(token, query, repositoryId = '*', maxChunkCount = 3) {
const url = `${AI_API_HOST}/v2/lm/document-grounding/retrieval/search`;
const payload = {
query: query,
filters: [
{
id: "filter-1",
searchConfiguration: {
maxChunkCount: maxChunkCount
},
dataRepositories: [repositoryId],
dataRepositoryType: "vector"
}
]
};
try {
const response = await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'AI-Resource-Group': resourceGroupId,
'Content-Type': 'application/json',
},
});
console.log(' 検索結果:');
console.log(JSON.stringify(response.data, null, 2));
} catch (err) {
if (err.response) {
console.error(' Retrieval Search エラー:', {
status: err.response.status,
statusText: err.response.statusText,
data: err.response.data,
});
} else {
console.error(' Retrieval Search エラー:', err.message);
}
}
}
// 実行処理
(async () => {
const userQuery = process.argv[2]; // 例: "What is SAP BTP?"
const repositoryId = process.argv[3] || '*'; // 任意:特定のrepositoryId or "*"(デフォルト)
if (!userQuery) {
console.log(' 使用方法: node 07_searchRetrieval.js "<query>" [repositoryId]');
return;
}
try {
console.log(' トークン取得中...');
const token = await getXsuaaToken();
console.log(` 検索実行中: "${userQuery}" 対象: ${repositoryId}`);
await searchRetrieval(token, userQuery, repositoryId);
console.log(' 検索完了!');
} catch (err) {
console.error(' 実行エラー:', err.message);
}
})();これまでで一連のGroundingの流れはさらいましたが、もう一つのベクトル化処理の方法として用意されているVector APIを実行してみます。下記の作業を順次行っていくことで、環境を整えることができます。
構成要素の包含関係は下記のイメージです。
下記のnodeスクリプトを実行することで、Collectionを作成できます。SAP AI Launchpadを確認する限り、先ほどのパイプラインでベクトル化処理を行う場合で言うところの「Repository」に該当する概念のようです。
node ./01_grounding/04_vectorAPI/07_manageCollection.js create <title: 例えばfolkTale> <embedding name: 例えばtext-embedding-ada-002>無事実行が完了すると、下記のようにCollectionが生成されたことが確認できます。
今回は、この中にDocumentを作成していきます。まずは下記のコマンドを利用して、今作成したRepository(Collection)のIDを取得します。
node ./01_grounding/04_vectorAPI/07_manageCollection.js listこのスクリプトでは下記の通りの処理が行われます。
// 一覧取得
async function listCollections(token) {
const url = `${AI_API_HOST}/v2/lm/document-grounding/vector/collections`;
const res = await axios.get(url, getRequestOptions(token));
console.log(' コレクション一覧:\n', JSON.stringify(res.data, null, 2));
}
// 実行部
(async () => {
const action = process.argv[2];
const arg1 = process.argv[3];
const arg2 = process.argv[4];
if (!action || !['list', 'get', 'create', 'delete', 'creationStatus', 'deletionStatus'].includes(action)) {
console.log(' 使用方法:');
console.log(' node 07_manageCollection.js list');
console.log(' node 07_manageCollection.js get <collectionId>');
console.log(' node 07_manageCollection.js create <title> <embeddingModel>');
console.log(' node 07_manageCollection.js delete <collectionId>');
console.log(' node 07_manageCollection.js creationStatus <collectionId>');
console.log(' node 07_manageCollection.js deletionStatus <collectionId>');
return;
}
try {
const token = await getXsuaaToken();
switch (action) {
case 'list':
await listCollections(token);
break;
case 'get':
await getCollection(token, arg1);
break;
case 'create':
await createCollection(token, arg1, arg2 || 'text-embedding-model');
break;
case 'delete':
await deleteCollection(token, arg1);
break;
case 'creationStatus':
await getCreationStatus(token, arg1);
break;
case 'deletionStatus':
await getDeletionStatus(token, arg1);
break;
}
console.log(' 完了!');
} catch (err) {
if (err.response) {
console.error(' エラー:', {
status: err.response.status,
statusText: err.response.statusText,
data: err.response.data,
});
} else {
console.error(' 実行エラー:', err.message);
}
}
})();次に、そのCollection IDを利用してCollection内にテキストを格納していきます。今回はdocsディレクトリに格納した、下記のMomotaro.txtを格納しましょう。
node ./01_grounding/04_vectorAPI/08_manageDocument.js create <collectionId: 直前で取得したCollection ID> <filePath: ./01_grounding/docs/Momotaro.txt>なお、SAP AI Core – Generative AI HubのGrounding機能に用意されたVector APIでは、文書を分割する機能は提供されていません。従って、開発者が外部で文書を分割し、整理された状態でAPIにリクエストを送る必要があります。詳細な処理としては、下記にスクリプトによる処理内容を記載の通り、今回は100文字ずつに分割しています。
const CHUNK_SIZE = 100; // 任意のチャンクサイズ
async function createDocument(token, collectionId, filePath) {
const url = `${AI_API_HOST}/v2/lm/document-grounding/vector/collections/${collectionId}/documents`;
const rawText = fs.readFileSync(filePath, 'utf8');
const chunks = [];
for (let i = 0; i < rawText.length; i += CHUNK_SIZE) {
chunks.push({
content: rawText.substring(i, i + CHUNK_SIZE),
metadata: [{ key: "index", value: [(i / CHUNK_SIZE + 1).toString()] }]
});
}
const payload = {
documents: [
{
metadata: [
{ key: "source", value: [path.basename(filePath)] }
],
chunks: chunks
}
]
};
const res = await axios.post(url, payload, getRequestOptions(token));
console.log(' ドキュメント作成成功:\n', JSON.stringify(res.data, null, 2));
}
(async () => {
const action = process.argv[2];
const arg1 = process.argv[3];
const arg2 = process.argv[4];
const filePath = process.argv[4];
const token = await getXsuaaToken();
switch (action) {
case 'list':
await listDocuments(token, arg1);
break;
case 'get':
await getDocument(token, arg1, arg2);
break;
case 'create':
if (!filePath || !fs.existsSync(filePath)) {
console.error(' ファイルが存在しません:', filePath);
return;
}
await createDocument(token, arg1, filePath);
break;
case 'update':
if (!filePath || !fs.existsSync(filePath)) {
console.error(' ファイルが存在しません:', filePath);
return;
}
await updateDocument(token, arg1, arg2, filePath);
break;
case 'delete':
await deleteDocument(token, arg1, arg2);
break;
case 'search':
await vectorSearch(token, arg1, arg2);
break;
default:
console.log(' 使用方法:');
console.log('node 08_manageDocument.js list <collectionId>');
console.log('node 08_manageDocument.js get <collectionId> <documentId>');
console.log('node 08_manageDocument.js create <collectionId> <filePath>');
console.log('node 08_manageDocument.js update <collectionId> <documentId> <filePath>');
console.log('node 08_manageDocument.js delete <collectionId> <documentId>');
console.log('node 08_manageDocument.js search "<query>" <collectionId>');
}
})();うまくいくと、下記のようにデータが格納されます。
最後に、ここに対して検索を行います。下記のnodeスクリプトを実行してください。
node ./01_grounding/04_vectorAPI/08_manageDocument.js search "<query: 質問文>" <collectionId: 直前で取得したCollection ID>無事、関連すると思われるチャンクが返ってきています。
処理内容は下記です。
async function vectorSearch(token, query, collectionId) {
const url = `${AI_API_HOST}/v2/lm/document-grounding/vector/search`;
const payload = {
query: query,
filters: [
{
id: "search-1",
collectionIds: [collectionId],
configuration: {},
documentMetadata: [],
chunkMetadata: [],
collectionMetadata: []
}
]
};
const res = await axios.post(url, payload, getRequestOptions(token));
console.log(' 検索結果:\n', JSON.stringify(res.data, null, 2));
}また、SAP AI Launchpadから検索することもできます。開発時には、やりやすい方を利用してください。
本記事では、2024年第4四半期にSAP AI Core – Generative AI Hubに追加された新機能「Grounding」について、技術的な背景から具体的なセットアップ手順、さらにはユースケースまでをハンズオン形式で詳しく紹介しました。
Groundingは、生成AIを業務で活用する上での大きな課題であった「信頼性」や「正確性」を補完するための重要な技術です。企業固有のデータや文書を活用し、生成AIの出力を現実の情報にしっかりと根付かせることで、業務に耐えうるアウトプットが可能になります。
特にSAP AI Coreのオーケストレーション機能と組み合わせることで、文書のベクトル化から検索、回答生成までを一貫して処理できるため、開発者にとっても扱いやすく、拡張性の高いソリューションとなっています。Pipeline APIによるバッチ処理や、Vector APIによる細かなデータ操作など、ユースケースに応じた柔軟なアプローチが用意されている点も魅力です。
今後は、社内ポータルやナレッジマネジメント、顧客対応チャットボットなど、さまざまな業務領域においてこのGrounding技術が重要な役割を果たしていくことが予想されます。ぜひ本記事を参考に、みなさまの業務や開発における生成AI活用の第一歩として、Generative AI HubのGrounding機能を試してみてください。
今回取り扱ったGrounding機能を、SAP AI CoreのOrchestration機能内で活用することができます。そちらについては、次のブログをご参照ください。
SAP AI Core Orchestration機能 を解き明かす
https://community.sap.com/t5/technology-blogs-by-sap/sap-ai-core-orchestration%E6%A9%9F%E8%83%BD-%E3...
免責事項
本ブログに記載されているすべての見解および意見は、私個人のものであり、個人的な立場で述べられたものです。SAPは、本ブログに掲載された内容について一切の責任を負いませんので、あらかじめご了承ください。
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 49 | |
| 49 | |
| 29 | |
| 23 | |
| 21 | |
| 15 | |
| 14 | |
| 13 | |
| 13 | |
| 12 |