Dify で『さくらのAI Engine』用カスタムツール(プラグイン)を作って呼び出してみた

さくらのAI Engine

先日リリースしました『さくらのAI Engine』ですが、想像を超えたフィードバックをいただき、早くも次の機能拡張を踏まえたアップデートの検討に向けた議論が活発に行われています。フィードバックをお寄せいただいた皆様、本当にありがとうございます。

こちらでハンズオン形式の記事を公開していますので、是非合わせてご覧ください。

Dify カスタムツール と プラグイン

AI Engineは、チャット、RAG、音声の文字起こしの3つがAPIとして提供されています。チャットAPIはOpenAPI互換ですが、細かい挙動の違いなどがあり、DifyのOpenAI用ブロックそのままでは利用できません。簡単にAI EngineをDifyから呼び出していただけるよう、カスタムツールを作りました。

一般的にはこのような外部モジュールはプラグインと呼ばれることが多いですが、Difyではカスタムツールが正式名称ですので、この記事では今後カスタムツールで統一します。

OpenAPI と Dify カスタムツール

OpenAPI は、API の仕様を標準化して書くための仕組みです。たとえば「このエンドポイントに GET リクエストを送るとこういうレスポンスが返ってきます」といった情報を、機械が読める形でまとめておけます。イメージとしては、API の取扱説明書を誰が見ても、そしてコンピュータが見ても理解できる形にしたものです。もともと Swagger という名前で知られていましたが、現在では OpenAPI Specification(OAS)という名称で世界的に使われています。開発者にとっては今や定番の技術であり、API を公開するならまず OpenAPI で仕様を記述しておく、という流れが一般的になっています。

OpenAPI の大きな利点は、仕様を一度書いてしまえば、それを活用してさまざまなことが自動化できる点にあります。たとえば、Swagger UI のようなツールを使えば、定義ファイルから見やすい API ドキュメントを自動で生成できます。これによって、わざわざ手作業で API リファレンスを整備する必要がなくなります。

こうした特徴から、OpenAPI は「API の設計図」と呼ばれます。

DifyカスタムツールはこのOpenAPIフォーマットをインポートすることで専用ブロック用テンプレートを作ることができます。

さっそくやってみる

このハンズオン手順ではDifyのアカウント開設が完了し、コンソールにアクセス出来ていることを想定しています。

1. カスタムツールの作成

Difyコンソール上部のツールをクリックします。

カスタムカスタムツールを作成する をクリックします。

以下のOpenAPI準拠スキーマを貼り付けます。

openapi: 3.0.3
info:
  title: Sakura AI - Chat Completions (Dify Tool)
  version: 1.0.0
  description: >
    Sakura AI の Chat Completions エンドポイントを Dify の Custom Tool として呼び出します。
    返却 JSON の choices[0].message.content をワークフロー側で text にマップしてください。
servers:
  - url: https://api.ai.sakura.ad.jp
paths:
  /v1/chat/completions:
    post:
      summary: Create chat completion
      operationId: createChatCompletion
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatRequest'
            examples:
              sample:
                value:
                  model: gpt-oss-120b
                  messages:
                    - role: system
                      content: "こんにちは!"
                  temperature: 0.7
                  max_tokens: 200
                  stream: false
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatResponse'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'Set as: Bearer <Token>'
  schemas:
    Message:
      type: object
      required: [role, content]
      properties:
        role:
          type: string
          enum: [system, user, assistant]
        content:
          type: string
    ChatRequest:
      type: object
      required: [model, messages]
      properties:
        model:
          type: string
          example: gpt-oss-120b
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
        temperature:
          type: number
          format: float
          default: 0.7
        max_tokens:
          type: integer
          default: 200
        stream:
          type: boolean
          default: false
    ChatResponse:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          example: chat.completion
        created:
          type: integer
        model:
          type: string
        choices:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              message:
                type: object
                properties:
                  role:
                    type: string
                  content:
                    type: string
                  refusal:
                    nullable: true
                  annotations:
                    nullable: true
                  audio:
                    nullable: true
                  function_call:
                    nullable: true
                  tool_calls:
                    type: array
                    items: {}
                  reasoning_content:
                    type: string
                    nullable: true
              logprobs:
                nullable: true
              finish_reason:
                type: string
                nullable: true
              stop_reason:
                nullable: true
        service_tier:
          nullable: true
        system_fingerprint:
          nullable: true
        usage:
          type: object
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer

自動で中身を読み取るツールにセットされています。

認証方法を設定します。<TOKEN>は皆さんの環境ごとに正しい値に置き換えてください。

保存をクリックするとカスタムツールが作成されます。

2. ワークフローの作成

2.1. カスタムツールブロックの設定

続いて先ほど作成したカスタムツールを呼び出すワークフローを作成します。

今度は画面上部からスタジオをクリックします。

最初から作成チャットフロー をクリックします。

適当な名前を付けて 作成する をクリックするとワークフローのひな型が起動します。

真ん中のLLMはOpenAI用ですので削除します。削除は選択して DEL キーで行えます。

先ほど作成したカスタムツールを挿入します。

あとでもう一つブロックを入れるので、回答ブロックを少し右に寄せておきます。

CREATECHATCOMPLETIONブロックを開いて設定画面から以下の値を入れます。

  • model : gpt-oss-120b
  • gpt-oss-120b:[ { "role": "user", "content": "{<変数:sys.query>}}" } ]

<変数:sys.query> の指定は少し注意が必要です。文字列で投入するのではなく、以下の様にシステムが自動で挿入する変数を指定します。

最初にブロックを開いたときに 変数を挿入する というリンクがありますので、それをクリックして選択します。

指定が完了したら右に向いている▷をクリックするとテスト実行がされます。

以下の様に何か出力が出ていれば成功です。

2.2. コード実行ブロックの設定

このカスタムツールブロックはAPIからの回答がJSONで出力されます。Difyの回答ブロックはtextを渡す必要があるので要素をより出す必要があります。

コードブロックを挿入して矢印でつなぎます。

コード実行ブロックを開いて入力変数の arg1json を指定します。

コードを JAVASCRIPT に変更して以下のスクリプトを貼り付けます。

// 関数テンプレート方式: 実行器が main(inputs_obj) を呼びます
function main(inputs_obj) {
  // arg1 に Tool の json を接続している前提
  const inData = inputs_obj?.arg1 ?? null;

  // ツールの json が配列の場合とオブジェクトの場合の両対応
  const data = Array.isArray(inData) ? inData[0] : inData;

  // 本文だけ取り出し(なければ空文字)
  const content = data?.choices?.[0]?.message?.content ?? "";

  // ★ 返り値オブジェクトのキー名 'result' は、出力変数名に合わせてください(String)
  return { result: String(content) };
}

2.3. 回答ブロックの設定

最後に、コードブロックがJSONから要素を抜き出したtextを受け取る様に設定します。

設定タブの応答からコードブロックの出力変数を指定できるようになっています。

2.4. テスト

ではプレビューからテストしてみましょう。

無事動作しました!

まとめ

本記事では『さくらのAI Engine』をDifyから呼び出す手順をまとめました。『さくらのAI Engine』には無料使用枠もありますので、是非使ってみて下さい。