SAPのGenerative AI Hubを使って簡単な画像認識アプリを作ってみた

## はじめに
この記事ではSAPの提供するGenerative AI Hubの機能を利用して、簡単な画像認識を行うFioriアプリを作ってみたので、どういうことをやってきたのか、実装の流れを簡単にご紹介したいと思います。
最後は作ったアプリケーションの使用例をいくつかご紹介します。

※以下、ざっくりした完成イメージ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/61328f85-40e2-696f-ea81-dc6966baa4cc.png" width=60%>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/249530be-60f6-d0b8-cb0a-32f0bd183c77.png" width=60%>

##### 想定読者
- SAPのCAPモデル(SAP Cloud Application Programming Model)開発の経験があり、Generative AI Hubを使ったアプリケーション開発に興味のある方
- CAPモデルのことはよく分からないが、SAPの提供する生成AI機能に興味のある方

※CAPモデル・・企業向けの高品質なサービスやアプリケーションを効率的に構築するための開発用フレームワーク

### 背景
SAPでは昨今、SAPワールドへのAI機能の拡充を進めており、以下のようなSAP Business AIモデルを展開しております。
SAPシステム全体にまたがるAIアシスタントであるJouleのほかに、既存SAPサービスへのAI機能の組み込みや、独自のアプリケーションにAI機能を組み込むカスタムAIアプリケーションを実装できる仕組みが用意されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/7499369e-fdf6-e527-0a02-f329f3254cb9.png)

出展: [Technology Blogs by SAP](https://community.sap.com/t5/technology-blogs-by-sap/sap-business-ai-overview-for-all/ba-p/13871365)

このカスタムAIアプリケーションを実装するための仕組みの一つがGenerative AI Hubと呼ばれるもの（という理解）で、これを使うことでアプリケーションにAI機能を簡単に統合できるようになっています。

この仕組みを使って、何か作ってみたいなぁと思っていたところ、以下のリンクのチュートリアルを発見しました。
>https://developers.sap.com/tutorials/ai-core-gpt4o-consumption.html

内容は、gpt-4oモデルを使って画像認識をしてみましょうというもので、Postmanだったり、Jupyter Notebookを使ってSAP AI Core(Generative AI Hubを構成する1サービス)で有効化したgpt-4oモデルを呼び出します。
これによってシーン検出、物体検出、文字認識等を行うといった、大変興味深い内容でした。

そんなこんなでこの内容にインスピレーションを受けて、これをSAPのアプリから呼び出して使ってみたい！
と思ったのが始まりでございます。

### 留意事項
- 重要な手順については出来る限り紹介したいと思いますが、本題と若干逸れる内容については割愛させていただいております。
- 突貫で作ったので、見た目や機能性は度外視しております。暖かい目で見守ってください！
- 無駄なロジックや設定をしてしまっている可能性もありますが、とりあえず動いているので良しとしています。変なことを書いていてもあまり気にしないでください^^

途中SAPのアプリケーション開発に関する込み入った話がいろいろ出てきて読みづらく感じるかもしれませんので、***もし苦痛になってきましたら、高速スクロールで後半の「使ってみた」まで飛ばしていただければと思います。(＾ω＾)***

## 準備
では初めに、アプリケーションを実装する上で最低限準備するものを記載します。
- SAP BTP環境
※2024年12月現在の時点で、trial環境ではSAP AI CoreやAI Launchpadといったサービスを使用できないようなので、Pay-As-You-GoやCPEAといった契約が必要です。
- SAP AI Coreサービスの有効化
※今回のようにGenerative AI Hubの機能を利用するには***Extendedプランでの登録が必須***。
- SAP AI Launchpadサービスの有効化
サービス登録後、gpt-4oモデルの設定、デプロイを行い、deploymentIDを発行します。
※standardプランとfreeプランがありますが、今回はfreeプランでも問題なし。standardプランの場合、AIモデルに渡すプロンプトテンプレートの事前定義などが可能です。

![スクリーンショット 2024-12-10 141533.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/7490e8a1-7a19-ae9c-1b84-e0dd933234f5.png)

- SAP HANA Cloud（データベース）有効化
- SAP Business Application Studio(BAS)の有効化
- SAP Build Codeの有効化

ちょっと準備が多くて大変ですが
このあたりは、SAPのチュートリアルなどを適宜ご参考にしていただければと思います。

### 参考リンク
https://developers.sap.com/tutorials/ai-core-generative-ai.html
https://developers.sap.com/tutorials/ai-core-genai-hana-vector.html
https://discovery-center.cloud.sap/missiondetail/4403/

## いざ実装
### アーキテクチャ
まず、今回実装するアプリケーションはざっくりこのような感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/b9c0e883-722b-ef0b-5383-28412f51240e.png)

CAPモデルを使ってバックエンドサービス作成し、この中でAIモデルを呼び出して使用します。
UIはSAPUI5を用いて、List Reportのテンプレートを使って作成します。

### プロジェクトの作成
SAP Build Codeから新規プロジェクトの作成を選択し、今回実装するアプリケーションを作成します。
※以下は作成済みの状態。Full-stack Applicationを選択しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/ceb6ac7d-23a5-31fd-8a3a-d8e3edd71c3e.png)

### CAPアプリケーションの作成
作成したプロジェクトを開くとBASが起動し、以下のようなStoryboardの画面が出てきます。

※以下は実装が終わった状態。新規の場合、中身は何も入っていません。
途中を全然撮れておらずすみません。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/c424675f-eaa0-88b8-57b1-69698511d36d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/864546f3-f1d2-b4cf-cbde-48c90912c953.png)
#### Entityの定義
ここからまずはEntityを定義する必要があります。
storyboardのDataModelsから、新規エンティティを追加します。
画像の位置にファイルが出来るので、そちらを編集します。※GUIでもできるみたいですが、ちょっと使いにくかったので直接編集しました。
今回は、認識した画像のURLとその結果が必要なので、6,7行目のみを追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/600abaa9-e653-96c5-9d8a-5179e1564186.png)

#### サービスの定義
storyboardのServicesから、新規サービスを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/970daf52-8b19-f4da-c830-98ddd77a3249.png)

6~8行目、16行目は手で追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/aacd046d-5dbf-9269-934e-b34fb76e7000.png)

6~8行目は生成AI機能呼び出しのため、16行目は画面に作成ボタンを付けるために追加しています。

### mta.yamlファイルの作成、プロジェクトのBuildなど
ここで、一旦プロジェクトのBuildを行いBTPへのアプリケーションのデプロイやデータベースへのテーブル登録などを行いましたが、ここはCAPプロジェクトのお作法的なところなので、今回は割愛させていただきます！すみません！m(_ _)m

結果的に、以下のようなインスタンスであったり、サービス、テーブルなどが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/01d98da2-0c05-5c15-c636-9c50f59cd03e.png)

![スクリーンショット 2024-12-10 142007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/b1c9399c-4cb7-3d28-4c45-ce25be70dc16.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/f2054eaa-fa04-3233-2241-43f788349fc1.png)

### Destinationの作成
次は、CAPプロジェクトからSAP AI CoreのAIモデルを呼び出すためのDestinationおよび、UI5アプリケーションからCAPプロジェクトを呼び出すためのDestinationをBTP Cockpitから設定します。
これらは後続の手順で必要となります。

**SAP AI Core向けの設定**
![スクリーンショット 2024-12-06 144750.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/1b407beb-83ed-9ea3-cad2-99d7073ee222.png)
※URLやClient ID/secretには、AI Coreの有効化時に設定した認証情報を入力します。
Token Service URLはURL末尾に ***/oauth/token***を追加したものを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/e29d4319-f154-6bed-c76b-f2d0adff620c.png)

**CAP向けの設定**
![スクリーンショット 2024-12-06 145532.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/dc9fd7bd-e7ac-a2cd-b385-3268ec740b2f.png)
認証情報は、前の手順でBTPに作成されたサービス（末尾-srv)のApplication RoutesのパスやService Bindingsの中で確認できるclientid/secretなどが入ります。

### UI5の追加
細かいCAPサービス実装がまだですが、ここで先にUI5アプリケーションを追加しておきます。
最後の最後でうまくつながらなかったら悲しいですからね。。

まずは最初に作ったCAPプロジェクトをBASで開いた状態で、File>New Project From Templateを選択します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/bdeaaef4-2b44-02dc-b67d-a31ab8d2043c.png" width=40%>
何を作るか聞かれますので、SAP Fiori generatorを選択します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/1406c34c-cb80-6698-c6d0-dcd283bfc610.png" width=30%>
さらにテンプレートとして、今回はList Reportを選択しました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/dc45845e-2d95-da07-7e7b-efe7eca0a052.png" width=80%>
Data sourceはLocal CAP Projectを選択し、使用するCAPプロジェクト（今回作成したもの）を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/e58d418b-14a8-2ecc-f9b6-dd82d3a920ff.png)

その後はEntityやアプリのタイトルなどを選択し完了となります。
以上により、プロジェクト内にUI5アプリケーションの型が作成されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/f37a0681-31bf-4e27-fdee-592bbcf6171e.png" width=40%>

### CAPサービスロジック追加
ここからいよいよAIモデルを呼び出す処理を追加していきます。
以下のように、[service.cds]と同じディレクトリに[service.js]を追加します。
※拡張子以外は同名にする必要があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/35e78ec5-0993-733c-ff2d-2742ee1e19d5.png" width=40%>

このファイルに必要なロジックを追加し、以下の状態にしました。
```js:service.js
const cds = require('@sap/cds')

const API_VERSION = process.env["AI_CORE_API_VERSION"] || "2023-05-15";

module.exports = class GenAIService extends cds.ApplicationService { init() {
    this.on('connectToGenAI', connectToGenAI)
    return super.init()
}}

async function connectToGenAI(req) {

    const prompt = req.data.prompt;

    var finalResponse = {Response:[]};

    const aiCoreService = await cds.connect.to("PROVIDER_AI_CORE_DESTINATION_HUB");
    const chatDeploymentIdGenAI = "xxxxxxxxxxxxx";

    const resourceGroupId = "default"; 
    const headers = { "Content-Type": "application/json", "AI-Resource-Group": resourceGroupId };
    const image_url = prompt

    const recognitionResponse = await getResponse(image_url, aiCoreService, chatDeploymentIdGenAI, headers);

    finalResponse.Response.push({
        "result": recognitionResponse
    });

    return finalResponse;

}

async function getResponse(image_url, aiCoreService, chatDeploymentIdGenAI, headers) {
    console.log("start");
    var question = "画像には何が映っていますか?";

    var chatResult = await aiCoreService.send({
        // @ts-ignore
        query: `POST /v2/inference/deployments/${chatDeploymentIdGenAI}/chat/completions?api-version=2024-06-01`,
        data: {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": question
                        },
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": image_url  // Use the direct image URL
                            }
                        }
                    ]
                }
            ]
        }
        ,
        headers: headers
    });
    
    var answer = chatResult.choices[0]?.message.content;

  return answer;

}
```
この中で、
**PROVIDER_AI_CORE_DESTINATION_HUB**はSAP AI CoreへのDestination
**xxxxxxxxxxxxx**はSAP AI Launchpad内でデプロイした、gpt-4oモデルのIDになります。
※SAP AI Launchpadから確認

以下の部分がこのアプリケーションの肝の部分であり、
事前に有効化しておいたAI APIにPOSTする際に、contentとして質問文、および認識したい画像のURLを渡しています。
```js
var chatResult = await aiCoreService.send({
        // @ts-ignore
        query: `POST /v2/inference/deployments/${chatDeploymentIdGenAI}/chat/completions?api-version=2024-06-01`,
        data: {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": question
                        },
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": image_url  // Use the direct image URL
                            }
                        }
                    ]
                }
            ]
        }
        ,
        headers: headers
    });
```
質問内容は今回以下のようにハードコーディングしていますが、ユーザ側で入力するように実装することももちろん可能です。
```
var question = "画像には何が映っていますか?";
```

CAPの実装内容については以上になります。

### UI5ロジックの追加
ようやく実装の終わりが見えてきました。。

最後にUI5で、CAP(AIサービス)の呼び出しができるよう、ロジックを追加していきます。

今回は、Object Pageの上部に[Check Image]ボタンを追加して、これのクリックをトリガーとしてAIサービスが起動するように実装しました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/d78309a0-93a7-0a76-1258-b439063bc69c.png" width=60%>

これを作るため、まずはUI5のwebappフォルダ上で右クリックし、**Open Guided development**を開きます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/7b0761c2-ee8f-9824-43cc-046a8e65ffb2.png" width=60%>

**Add a custom action to a page using extensions**を選択し、ガイドに従い[Check Image]ボタンを追加します。
※詳細は割愛させていただきます！すみません。
ここまでで、すでに[Check Image]ボタンの押下とサービス呼び出しが紐づけられている状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/5d3f7a06-f2b7-6a98-640a-8f40a1a8b852.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/2b070d1b-40d4-3074-98c1-61cc185d4b4e.png)

これによって[ObjectPageExt.js]ファイルが作成されるため、ここに具体的に必要なロジックを追加していく流れになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/0de76f50-73da-8cd0-6851-0997e9ae0ad1.png" width=40%>

最終的にロジックは以下のようにしました。
```js:ObjectPage.Ext
sap.ui.define([
    "sap/m/MessageToast",
    "sap/ui/model/json/JSONModel"
], function(MessageToast,JSONModel) {
    'use strict';

    return {
        GenAIService: async function(oEvent) {
            //MessageToast.show("Custom handler invoked.");

            var sPrompt = this._view.getBindingContext().getObject().imageURL;

            var oGlobalBusyDialog = new sap.m.BusyDialog();
		    oGlobalBusyDialog.open();
            try {
                const response = await fetch("/service/imageTestService/connectToGenAI", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json"
                    },
                    body: JSON.stringify({
                        prompt: sPrompt
                    })
                });
                if (response.ok) {
                    const result = (await response.json()) ;
                    console.log(result);
                    if(result.value.Response) {
                       oEvent.setProperty(
                            "result",
                            result.value.Response[0].result
                       );
                    }
                    oGlobalBusyDialog.close();
                    console.log("success")
                    
                }
            } catch (error) {
                oGlobalBusyDialog.close();
                MessageToast.show(error);
                console.log(error);
            } finally {
                oGlobalBusyDialog.close();
            }            
        }
    };
});
```

**sPrompt**に画面から入力した画像URLを入れて、サービスを呼び出します。

あとは途中の手順と同じくサービスのBuildとデプロイをしたら完了になります。

途中端折ったところもあり、駆け足となってしまいましたが、、実装の流れは以上です！

ここからは、作成したアプリを実際に使ってみたいと思います。

## 使ってみた
アプリを起動するとこのような一覧画面が出てきます。
※既に動かしているので、何件か登録されている状態。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/2311d7f0-3589-c0a9-c730-1cd0c24098fe.png)

ここで、[作成]ボタンを押すと、以下のような画面が出てきますので、読み取りたい画像のURLを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/291e9517-2be1-465c-e7b5-209ac51971e0.png)

### シーン認識
例えば、この記事の冒頭に貼った風景写真の画像URLを入れてみます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/61328f85-40e2-696f-ea81-dc6966baa4cc.png" width=100%>

>引用:https://publicdomainq.net/napoli-coast-photo-0078269/

[Check Image]ボタンを押下すると、サービスが呼び出しが開始されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/fa540256-e0b6-90c9-c820-c262e819c4b2.png)

数秒後、以下のように **画像には何が映っていますか?** という質問に対する回答が返ってきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/28301e74-41ed-3eaf-062d-0caba5d0faaf.png)


### 物体検出
続いて、先ほどの例と少し近いですが、以下のわんこの画像を読み取ってみて、何が何匹映っているか回答できるかを試してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/c68f486a-29f2-9ea5-3725-00dcdc964c11.png)

>引用:https://publicdomainq.net/dogs-animal-0026007/

プロンプトは以下のように書き換えました。

```
var question = "画像には何がどれだけの数映っていますか？";
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/20af6acb-46f1-3887-4caf-50422f82360c.png)


回答を出してくれましたが、若干間違えてますね。。（上段左から2番目の真っ黒い子を認識できなかったのではと予想）
もう少し分かりやすい画像データであれば、商品の在庫を数えるとか、何かに使えそうな感じがします。

### 文字や構成の認識
続いて、冒頭に貼ったSAP Business AIに関して記した画像を読み取って、内容を説明できるか試してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/918a2ca9-e621-2d24-46d3-1456e40672bf.png)
出展: [Technology Blogs by SAP](https://community.sap.com/t5/technology-blogs-by-sap/sap-business-ai-overview-for-all/ba-p/13871365)

プロンプトは以下のように書き換えました。
```
var question = "画像に描かれた内容を説明してください";
```
結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/7763ee8b-6c4e-c3ef-0219-fffa1478915c.png)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936308/8cfdb18a-8670-06e3-b479-701ecae46687.png" width=80%>

内容を知っている上で回答を見ると少し **?** な部分もありますが、読み取りの精度は中々のものです。
なお、画質の問題かもしれませんが文字が小さいロゴの部分はしれっとスルーしているように見えますね。

この機能についても、そのままAIモデルを呼び出すだけでも、ちょっとしたメモ書きや画像のみのデータを読み取ってテキストを起こしたいシーンなど、使い道が色々とありそうです。

※ここで試した内容は冒頭紹介した以下のチュートリアルの中でも確かめることができます。単に素のgpt-4oで出来ることを検証したい場合は、これだけで十分かと思います。
>https://developers.sap.com/tutorials/ai-core-gpt4o-consumption.html

## 今後の展望
今回のアプリケーションでは、gpt-4oモデルを呼び出して、画像と質問内容のテキストだけを投げるという、最もシンプルな構成で実装を行いました。
よって、回答内容の精度＝gpt-4oの性能ということになる理解ですが、単純にAIモデルを呼び出して利用するだけでもある程度正確な回答を得られることが分かります。

さて、今回は実際のユースケースは一旦度外視して、ざっくりした利用例を紹介してみましたが
実際に画像認識が必要なシチュエーションを考えると、例えば
- 倉庫や店舗で、商品や在庫の自動分類や不良品が含まれていないかどうかの確認を行う
- 作業現場での安全確認や異常検知を行う
- 顔認証による本人確認

といったものも浮かんできます。

これらの要件を実現するには、汎用モデル（gpt-4oなど）が学習済みのデータでは不十分であり、
企業が扱う商品や、現場の様々な状況、社員の顔社員といった、独自のデータを事前に大量に学習することが必要になってくるかと思います。

SAPではこうした企業固有のデータを学習して、独自のデータモデルを構築する仕組みや、RAG(Retrieval Augmented Generation)アプリケーションを構築するための基盤も提供されています。

これらの仕組みを使いこなすことが、今後非常に重要になってくるかと思いますので、是非時間を見つけていろいろ触ってみながら習得していきたいと思います。

## まとめ
今回、SAPのGenerative AI Hubを使った簡単な画像認識アプリの実装例をご紹介しました。

※本当はスマートフォンから撮った写真を、構築したモバイルアプリから認識させたり、
画像のURLではなく、ファイルをローカルフォルダからもアップロードできるUIにしたいな、などと何となく考えていたのですが、スキルと時間不足のため、とりあえずは今回の形に落ち着きました^^;

アプリで出来ることはchatGPTの劣化版（会話できないため）になりますが、
SAP Generative AI Hubを使ったAIアプリケーション構築の良い練習になったと思います。

これを基本として、
- 用途によって使用するモデルを切り替える
- ERPのデータを活用する
- RAGアプリケーションを構築する
- 独自のデータモデルを構築し、組み込む

といったように今後は少しずつ出来ること増やしていって、アップグレードしたものを作っていきたいと思います。

途中端折ったりして分かりにくいところが多々あったかと思いますが、見ていただいた方
ありがとうございました！