watsonx OrchestrateとGeminiを連携してみる

## Gemini2.0が公開されました。

GeminiはGoogleが提供しているLLMおよびAPIですが、昨年末に2.0が公開されました。Tool呼び出しや、コード・インタープリター、構造化出力等、様々な機能を提供しますが、コンテキスト・ウィンドウが非常に大きい点や、マルチ・モーダルに対応しているところも大きな特徴の一つです。

この記事では、watsonx OrchestrateからGeminiを呼び出すための手順について説明します。シナリオとしてはマルチ・モーダルな特徴を活かすために、書類の画像を抽出するといったものを実装してみようと思います。

## API Keyの取得
geminiのAPIKeyは以下のURLから取得可能です。
https://aistudio.google.com/apikey

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/026aca45-ede0-40ba-9156-adf8fd728e89.png)

## POSTMANによるテスト
今回は、車検証と免許証のサンプル画像を国土交通省のサイトより取得し、1つのPDFファイルにまとめて、何が含まれているかを判定することにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/3e2d3f7c-6cb0-481a-8ff5-66613c5692ff.png)

POSTMANで実際に呼び出してみたところ、正しく書類を判定してくれました。なお、PDFファイルはbase64でエンコードしたテキスト形式で渡す必要があります。watsonx Orchestrate上でファイルをエンコードする方法については後述しますが、それ以外にもオンラインのエンコードサービスを使用したり、windowsではcertutilコマンドを使用したりすることでもエンコードは可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/b98c385e-01c3-4410-9a86-525d0b634882.png)

なお、書類の種類だけではなく、キー・バリュー形式で項目を出力してくださいなどと、プロンプトで指定すればかなり正確に文字を読み取り、項目を分類してくれます。

## watsonx Orchestrateへのスキルの登録
watsonx OrchestrateからAPIを呼び出すには、OpenAPI形式でAPIを定義する必要があります。

今回は以下のような定義を作成しました。
ここでのポイントは、現状watsonx Orchestrateは one of/any of などをサポートしていないので、one ofと同様の表現をする場合には、可能性のある両方を含む形式で型を定義する必要があるという事です。
また、認証については、ダミーの認証を定義し、カタログからのスキルの追加とアプリケーションの接続をすることも忘れないでください。

```gemini.yaml
openapi: 3.0.0
info:
  title: Gemini AI Content Generation API
  version: 3.0.0
  description: call gemini to test

servers:
- url: https://generativelanguage.googleapis.com

paths:
  /v1beta/models/gemini-2.0-flash:generateContent:
    post:
      summary: Generate content using the Gemini AI model
      description: This endpoint generates text content based on the input provided in the request body using the Gemini AI model.
      operationId: generateContent
      parameters:
        - name: key
          in: query
          description: API key for authentication
          required: true
          schema:
            type: string
      requestBody:
        description: Request payload to generate content
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                contents:
                  type: array
                  items:
                    type: object
                    properties:
                      parts:
                        type: array
                        items:
                          type: object
                          properties:
                            text:
                              type: string
                              description: Text to generate content for
                              example: "Explain how AI works"
                            inlineData:
                              type: object
                              description: Text to generate content for
                              properties:
                                mimeType:
                                  type: string
                                data:
                                  type: string

                  
      responses:
        '200':
          description: Successful content generation
          content:
            application/json:
              schema:
                type: object
                properties:
                  candidates:
                    type: array
                    items:
                      type: object
                      properties:
                        content:
                          type: object
                          properties:
                            parts:
                              type: array
                              items:
                                type: object
                                properties:
                                  text:
                                    type: string
                                    description: Generated text content
                        role:
                          type: string
                          description: Role of the content generator (e.g., model)
                        finishReason:
                          type: string
                          description: Reason for finishing the generation
                        avgLogprobs:
                          type: number
                          description: Average log probabilities of the generation
                  usageMetadata:
                    type: object
                    properties:
                      promptTokenCount:
                        type: integer
                        description: Number of tokens in the prompt
                      candidatesTokenCount:
                        type: integer
                        description: Number of tokens in the candidates
                      totalTokenCount:
                        type: integer
                        description: Total number of tokens used
                  modelVersion:
                    type: string
                    description: Version of the AI model used
        '400':
          description: Bad request, invalid input
        '401':
          description: Unauthorized, invalid API key
        '500':
          description: Internal server error
          
components:
  securitySchemes:
    tokenAuth:
      type: apiKey
      in: header
      name: api-key
```
作成したOpenAPI定義をwatsonx Orchestrateに読み込むことで、スキルとして呼び出すことが可能になります。

## workflowによるラッピング
GeminiのAPIの入出力IFは汎用的に作られているため、チャット画面からそのまま呼び出すのには適していません。そこで、ファイルだけ渡してあげると書類の内容が返ってくるようにwatsonx Orchestrateのワークフローでラッピングしてあげます。

次のようなワークフロー定義し、ワークフローの入力としては、base64エンコードされたStringを、出力としては、結果のテキストのみを出力するように構成します。プロンプトもあらかじめ変数の中に埋め込んでおきます。この辺りは多少慣れが必要ですが、慣れてしまえばスムーズに定義することが可能です。

生成AI系のAPIは、汎用性を持たせるために配列が使われていることが多いのですが、AIAgentやチャットから呼び出す際にはできるだけシンプルなインターフェースの方が使い勝手は良くなるため、Agentから活用されることを意識したAPI形式や、APIの複雑さをLLMで吸収する手法なども検討する必要がありそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/fa7ad605-a398-4c31-b2d8-9cc81bd8e19d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/210f2607-ecc0-42a5-9865-57e8ff459a65.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/50768812-51c6-43e3-9b98-d68f0018674a.png)

ワークフローのテストをしてみたところ、正しくテキストが返ってきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/b287eb73-bccf-4350-98fa-664ff16b0cd5.png)

## workflowのデプロイと、OpenAPI定義の修正
作製したワークフローをパブリッシュしますが、このままだとチャットからBase64エンコードしたテキストを入力しなければならないため、公開されたスキルを修正します。
スキルの一覧から公開されたスキルを一度エクスポートし、inputFileの形式として
```
"format":"binary"
```
を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/b084291a-e900-425a-bad7-62d432fb3959.png)

再びファイルをインポートして、スキルを公開し、カタログから追加します。フレーズとして、書類の判定といったキーワードを登録しておくと良いでしょう。

## スキルのテスト
では、実際にチャットから呼び出してみましょう。
次の様にファイルを追加して呼び出すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/60557b6a-a1bc-4510-979c-d49ec4c4ccac.png)
正しく結果が返ってきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117736/fc93a46d-e0e8-4295-aeba-e284fe2a3669.png)

# まとめ
この記事ではGeminiをスキルとして登録し、watsonx Orchestrateから呼び出す手順について説明しました。Geminiの特徴であるマルチ・モーダルやコンテキスト・ウィンドウの大きさを活かせるシナリオで活用可能です。

単にGeminiのAPIを呼び出すだけならわざわざwatsonx Orchestrateを使う必要性は薄いですが、watsonx Orchestrateを用いることで、ルール・エンジンや他システムと連携した業務フローに適用できます、例えば、書類の判別や文字の読み取りといった作業をGeminiに行わせ、その結果を受けて、watsonx Orchestrate側のルール・エンジンで必要な書類が足りているかを判定し、足りている場合には、システム連携して処理を完了、不足している場合には他の担当者に確認を依頼するといったシナリオが考えられます。今後そのあたりの記事も書いてみたいと思います。