Skip to content

音声入力で Azure OpenAI Service の Function Calling を使って文脈に基づいた Microsoft Graph Search API 検索結果を音声で応答します。

Notifications You must be signed in to change notification settings

TK3214-MS/POC-AOAI-FunctionCalling

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 

Repository files navigation

アーキテクチャー

今回のアーキテクチャーは以下の通りです。

00

簡単な流れは以下の通りです。

  1. Power Apps より音声で質問を入力する。
  2. 入力された音声データが base64 形式でPower Automate フローに渡される。
  3. 音声データを Azure Function が WMV フォーマットに変換する。
  4. 音声データが Azure Speech Service によりテキストに変換される。
  5. 変換後テキストを元に Azure Open AI Service が社内ドキュメント検索か通常の応答かを判断する。
  6. 社内ドキュメント検索が必要と判断された場合、事前定義された JSON スキーマを元に Graph API での Microsoft Search を実行する。

構成手順

以下を構成、実装する事で動作確認用環境の構成が可能です。

事前準備

Azure Open AI リソースの作成

  1. 手順ページに則り、Azure Open AI Service リソースを作成します。

  2. 手順ページに則り、モデルを作成します。

    ※本サンプルでは gpt-35-turbo-16k を利用しました。

SharePoint Online ドキュメントライブラリの準備

  1. 検索対象としたいドキュメントをアップロードします。

Azure AI Speech Service リソースの作成

Speech Service リソース (無料または有料レベル) を Azure アカウントに追加するには:

  1. お使いの Microsoft アカウントを使用して Azure portal にサインインします。

  2. ポータルの左上にある [Create a resource](リソースの作成) を選択します。 [リソースの作成] が表示されない場合は、画面左上の折りたたまれたメニューを選択することで、いつでも見つけることができます。

  3. 新規 ウィンドウで、検索ボックスに「speech」と入力し、Enter キーを押します。

  4. 検索結果で、 [Speech] を選択します。

    :::image type="content" source="media/index/speech-search.png" alt-text="Azure portal で Speech リソースを作成します。":::

  5. [作成] を選択して、次のことを行います。

    • 新しいリソースに一意の名前を指定します。 この名前は、同じサービスに関連付けられた複数のサブスクリプションを区別するのに役立ちます。
    • 新しいリソースが関連付けられている Azure サブスクリプションを選択して、料金の課金方法を決定します。 Azure portal で Azure サブスクリプションを作成する方法の概要はこちらにあります。
    • リソースが使用されるリージョンを選択します。 Azure は、世界中のさまざまな地域で一般的に利用できるグローバル クラウド プラットフォームです。 パフォーマンスを最適にするには、ユーザーまたはアプリケーションが実行されている場所に最も近いリージョンを選択します。 音声サービスの可用性は、リージョンによって異なります。 サポートされているリージョンにリソースが作成されていることを確認してください。 音声サービスがサポートされているリージョンに関するページを参照してください。
    • 無料 (F0) または有料 (S0) の価格レベルのどちらかを選択します。 各レベルの価格と使用量クォータの完全な情報については、 [価格の詳細を表示] を選択するか、音声サービスの価格に関するページを参照してください。 リソースの制限については、「Azure Cognitive Services の制限」を参照してください。
    • この Speech サブスクリプションの新しいリソース グループを作成するか、既存のリソース グループにサブスクリプションを割り当てます。 リソース グループは、さまざまな Azure サブスクリプションを整理しておくのに役立ちます。
    • [作成] を選択します これでデプロイの概要に移動し、デプロイの進行状況を示すメッセージが表示されます。

新しい音声リソースを展開するまでに少し時間がかかります。

キーと場所/リージョンを見つける

完成したデプロイのキーと場所/リージョンを見つけるには、次の手順に従います。

  1. お使いの Microsoft アカウントを使用して Azure portal にサインインします。

  2. [すべてのリソース] を選択し、Cognitive Services リソースの名前を選択します。

  3. 左ペインの [リソース管理] から [Keys and Endpoint](キーとエンドポイント) を選択します。

各サブスクリプションには 2 つのキーがあります。アプリケーションでどちらのキーを使用しても構いません。 キーをコード エディターやその他の場所にコピーして貼り付けるには、各キーの横にあるコピー ボタンを選択し、ウィンドウを切り替えてクリップボードの内容を目的の場所に貼り付けます。

さらに、SDK 呼び出しのリージョン ID (、 など) である 値をコピーします。 SDK 呼び出しのリージョン ID (westuswesteurope など) です。

Important

これらのサブスクリプション キーは、Cognitive Service API にアクセスするために使用されます。 キーを共有しないでください。 Azure Key Vault を使用するなどして、安全に保管してください。 これらのキーを定期的に再生成することもお勧めします。 API 呼び出しを行うために必要なキーは 1 つだけです。 最初のキーを再生成するときに、2 番目のキーを使用してサービスに継続的にアクセスすることができます。

Function App リソースの作成

  1. 手順ページに則り、Function App リソースを作成します。

  2. 本サンプルでは以下設定でリソースを作成しました。

    設定名 設定値
    Runtime stack .NET
    Version 6 (LTS)
    Operating System Windows
    Hosting option Consumption

    ※その他項目は任意で設定して下さい。

構成

Function App の構成

  1. 作成した Function App リソースから新しい関数を以下設定値で作成します。

    01

  2. 作成した関数の"Code+Test"を選択し、run.csxfunction.jsonの内容を貼り付けます。

  3. 変更を保存し、"Get function URL"より Power Automate より HTTP 要求を行うターゲット URL をコピーしておきます。

    02

  4. Function App リソースのトップ画面に戻り、"Advanced Tool" から Kudu を開きます。

  5. "Debug Console" に "CMD" を選択し、以下パスまで移動します。

    C:\home\site\wwwroot\

  6. "ConvertAudioFormatUsingFFmpeg" ディレクトリを新規作成し、こちらよりダウンロードした Windows 向け FFmpeg 実行ファイルをアップロードします。

    • ffmpeg.exe
    • ffplay.exe
    • ffprobe.exe

Power Apps の構成

以下手順を構成頂くと以下アプリが完成します。

03

No コントロール種別 コントロール名
マイク Microphone1
ボタン Button1
テキストラベル AudioJSON
テキストラベル TextOutput
オーディオ Audio1
  1. マイクコントロールへのプロパティ追加

    プロパティ名
    OnStop ClearCollect(AudioCollection,Microphone1.Audio);
    Set(JSONValue,JSON(AudioCollection,JSONFormat.IncludeBinaryData));
  2. ボタンコントロールへのプロパティ追加

    プロパティ名
    OnSelect Set(DesiredOutput,'POC-Convert-Speech2Text'.Run(JSON(First(AudioCollection),JSONFormat.IncludeBinaryData)));
    Set(TTSAudio,DesiredOutput.audio);
    Set(VisibleClip, true);
    Set(playAudio,true);
  3. テキストラベルコントロール(AudioJSON)へのプロパティ追加

    プロパティ名
    Text Text(JSONValue)
  4. テキストラベルコントロール(TextOutput)へのプロパティ追加

    プロパティ名
    Text DesiredOutput.text

    [!NOTE]
    複数のエラーが表示されますが、Power Automate フローを関連付けると解消されます。

Power Automate の構成

以下手順を構成頂くと以下フローが完成します。

04

  1. Power Apps トリガーの構成

05

  1. 変数の初期化 (Initialize AOAI URL) アクションの構成

    06

    名前 種類
    AOAI-URL 文字列 https://[AOAI ENDPOINT].openai.azure.com/openai/deployments/[MODEL NAME]]/chat/completions?api-version=2023-07-01-preview
  2. 変数の初期化 (Initialize AOAI Key) アクションの構成

    07

    名前 種類
    AOAI-Key 文字列 [AOAI KEY]
  3. 作成 (Compose PowerApps Input) アクションの構成

    08

    入力
    triggerBody()['ComposePowerAppsInput_入力']

    ※式(Power Fx)として入力

  4. JSON の解析 (Parse Power Apps Input) アクションの構成

    09

    コンテンツ スキーマ
    outputs('Compose_PowerApps_Input') {
    "type": "object",
    "properties": {
    "Value": {
    "type": "string"
    }
    }
    }
  5. HTTP (Call Media Format Conversion) アクションの構成

    10

    方法 URI 本文
    POST [Function App URL] body('Parse_Power_Apps_Input')?['Value']
  6. HTTP (Call Speech2Text Service) アクションの構成

    11

    方法 URI ヘッダー 本文
    POST https://japaneast.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=ja-JP&format=detailed Ocp-Apim-Subscription-Key:[Speech Service Key]
    Content-type:audio/wav
    body('Call_Media_Format_Conversion')
  7. JSON の解析 (Parse Speech2Text Output) アクションの構成

    12

    コンテンツ スキーマ
    body('Call_Speech2Text_Service') {
    "type": "object",
    "properties": {
    "RecognitionStatus": {
    "type": "string"
    },
    "Offset": {
    "type": "integer"
    },
    "Duration": {
    "type": "integer"
    },
    "NBest": {
    "type": "array",
    "items": {
    "type": "object",
    "properties": {
    "Confidence": {
    "type": "number"
    },
    "Lexical": {
    "type": "string"
    },
    "ITN": {
    "type": "string"
    },
    "MaskedITN": {
    "type": "string"
    },
    "Display": {
    "type": "string"
    }
    },
    "required": [
    "Confidence",
    "Lexical",
    "ITN",
    "MaskedITN",
    "Display"
    ]
    }
    },
    "DisplayText": {
    "type": "string"
    }
    }
    }
  8. HTTP (Call AOAI for Output) アクションの構成

    13

    方法 URI ヘッダー 本文
    POST variables('AOAI-URL') api-key:variables('AOAI-Key')
    Content-type:application/json
    以下を参照
    {
        "frequency_penalty": 0,
        "max_tokens": 3000,
        "messages": [
            {
                "content": "You are AI assisstant.",
                "role": "system"
            },
            {
                "content": "@{body('Parse_Speech2Text_Output')?['DisplayText']}",
                "role": "user"
            }
        ],
        "functions": [
            {
                "name": "microsoft_search_api",
                "description": "手順やルールのドキュメントに関する質問をされるとMicrosoft Graph Search APIを用いて検索をします。",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "requests": {
                        "type": "object",
                    "properties": {
                        "entityTypes": {
                        "type": "array",
                        "description": "検索の対象にするリソースの種類。不明な場合はすべての項目を含めます。",
                        "items": {
                            "type": "string",
                            "enum": [
                                "site",
                                "list",
                                "listItem",
                                "drive",
                                "driveItem",
                                "message",
                                "event"
                                ]
                            }
                        },
                        "query": {
                            "type": "object",
                            "properties": {
                            "queryString": {
                            "type": "string",
                            "description": "検索キーワード。半角スペース区切り。"
                            }
                        }
                    }
                }
            }
        }
    }
    }
    ],
    "presence_penalty": 0,
    "stop": null,
    "temperature": 0
    }
    
  9. JSON の解析 (Parse AOAI Output) アクションの構成

    14

    コンテンツ スキーマ
    body('Call_AOAI_for_Output') 以下を参照
        {
        "type": "object",
        "properties": {
            "id": {
                "type": "string"
            },
            "object": {
                "type": "string"
            },
            "created": {
                "type": "integer"
            },
            "model": {
                "type": "string"
            },
            "prompt_filter_results": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "prompt_index": {
                            "type": "integer"
                        },
                        "content_filter_results": {
                            "type": "object",
                            "properties": {
                                "hate": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "self_harm": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "sexual": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "violence": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "required": [
                        "prompt_index",
                        "content_filter_results"
                    ]
                }
            },
            "choices": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "index": {
                            "type": "integer"
                        },
                        "finish_reason": {
                            "type": "string"
                        },
                        "message": {
                            "type": "object",
                            "properties": {
                                "role": {
                                    "type": "string"
                                },
                                "function_call": {
                                    "type": "object",
                                    "properties": {
                                        "name": {
                                            "type": "string"
                                        },
                                        "arguments": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        },
                        "content_filter_results": {
                            "type": "object",
                            "properties": {}
                        }
                    },
                    "required": [
                        "index",
                        "finish_reason",
                        "message",
                        "content_filter_results"
                    ]
                }
            },
            "usage": {
                "type": "object",
                "properties": {
                    "completion_tokens": {
                        "type": "integer"
                    },
                    "prompt_tokens": {
                        "type": "integer"
                    },
                    "total_tokens": {
                        "type": "integer"
                    }
                }
            }
        }
    }
    
  10. 作成 (Pick first argument) アクションの構成

    15

    入力
    first(body('Parse_AOAI_Output')?['choices'])

    ※式(Power Fx)として入力

  11. JSON の解析 (Parse argument item) アクションの構成

    16

    コンテンツ スキーマ
    outputs('Pick_first_argument') 以下を参照
    {
        "type": "object",
        "properties": {
            "index": {
                "type": "integer"
            },
            "finish_reason": {
                "type": "string"
            },
            "message": {
                "type": "object",
                "properties": {
                    "role": {
                        "type": "string"
                    },
                    "function_call": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            },
                            "arguments": {
                                "type": "string"
                            }
                        }
                    }
                }
            },
            "content_filter_results": {
                "type": "object",
                "properties": {}
            }
        }
    }
    
  12. 変数の初期化 (Initialize Argument) アクションの構成

    17

    名前 種類
    Argument オブジェクト [空白]
  13. 変数の設定 (Set Argument) アクションの構成

    18

    名前
    Argument {
    "requests": [
    {
    "entityTypes": [
    "listItem"
    ],
    "query": {
    "queryString": "@{body('Parse_argument_item')?['message']?['function_call']?['arguments']?['queryString']}"
    }
    }
    ]
    }
  14. 条件コントロールの構成

    19

    条件
    body('Parse_argument_item')?['finish_reason'] 次の値に等しい function_call

はいの場合

  1. HTTP with Azure AD (Request Search via Graph API) の構成

    20

    方法 要求の URL 要求の本文
    POST https://graph.microsoft.com/v1.0/search/query variables('Argument')
  2. HTTP (Validate Answer via AOAI) アクションの構成

    21

    方法 URI ヘッダー 本文
    POST variables('AOAI-URL') api-key:variables('AOAI-Key')
    Content-type:application/json
    以下を参照
    {
        "frequency_penalty": 0,
        "max_tokens": 3000,
        "messages": [
        {
            "content": "You are AI assisstant.",
            "role": "system"
        },
        {
            "content": "@{body('Parse_Speech2Text_Output')?['DisplayText']}",
            "role": "user"
        },
        {
            "content": "@{body('Request_Search_via_Graph_API')}",
            "role": "function",
            "name": "microsoft_search_api"
        }
        ],
        "functions": [
        {
            "name": "microsoft_search_api",
            "description": "手順やルールのドキュメントに関する質問をされるとMicrosoft Graph Search APIを用いて検索をします。",
            "parameters": {
            "type": "object",
            "properties": {
                "requests": {
                "type": "object",
                "properties": {
                    "entityTypes": {
                    "type": "array",
                    "description": "検索の対象にするリソースの種類。不明な場合はすべての項目を含めます。",
                    "items": {
                        "type": "string",
                        "enum": [
                        "site",
                        "list",
                        "listItem",
                        "drive",
                        "driveItem",
                        "message",
                        "event"
                        ]
                    }
                    },
                    "query": {
                    "type": "object",
                    "properties": {
                        "queryString": {
                        "type": "string",
                        "description": "検索キーワード。半角スペース区切り。"
                        }
                    }
                    }
                }
                }
            }
            }
        }
        ],
        "presence_penalty": 0,
        "stop": null,
        "temperature": 0
    }
  3. JSON の解析 (Parse Search API Output) アクションの構成

    22

    コンテンツ スキーマ
    body('Validate_Answer_via_AOAI') 以下を参照
    {
        "type": "object",
        "properties": {
            "id": {
                "type": "string"
            },
            "object": {
                "type": "string"
            },
            "created": {
                "type": "integer"
            },
            "model": {
                "type": "string"
            },
            "prompt_filter_results": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "prompt_index": {
                            "type": "integer"
                        },
                        "content_filter_results": {
                            "type": "object",
                            "properties": {
                                "hate": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "self_harm": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "sexual": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "violence": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "required": [
                        "prompt_index",
                        "content_filter_results"
                    ]
                }
            },
            "choices": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "index": {
                            "type": "integer"
                        },
                        "finish_reason": {
                            "type": "string"
                        },
                        "message": {
                            "type": "object",
                            "properties": {
                                "role": {
                                    "type": "string"
                                },
                                "content": {
                                    "type": "string"
                                }
                            }
                        },
                        "content_filter_results": {
                            "type": "object",
                            "properties": {
                                "hate": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "self_harm": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "sexual": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "violence": {
                                    "type": "object",
                                    "properties": {
                                        "filtered": {
                                            "type": "boolean"
                                        },
                                        "severity": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "required": [
                        "index",
                        "finish_reason",
                        "message",
                        "content_filter_results"
                    ]
                }
            },
            "usage": {
                "type": "object",
                "properties": {
                    "completion_tokens": {
                        "type": "integer"
                    },
                    "prompt_tokens": {
                        "type": "integer"
                    },
                    "total_tokens": {
                        "type": "integer"
                    }
                }
            }
        }
    }
  4. 作成 (Pick first content) アクションの構成

    23

    入力
    first(body('Parse_Search_API_Output')?['choices'])
  5. JSON の解析 (Parse Content) アクションの構成

    24

    コンテンツ スキーマ
    outputs('Pick_first_content') 以下を参照
    {
        "type": "object",
        "properties": {
            "index": {
                "type": "integer"
            },
            "finish_reason": {
                "type": "string"
            },
            "message": {
                "type": "object",
                "properties": {
                    "role": {
                        "type": "string"
                    },
                    "content": {
                        "type": "string"
                    }
                }
            },
            "content_filter_results": {
                "type": "object",
                "properties": {
                    "hate": {
                        "type": "object",
                        "properties": {
                            "filtered": {
                                "type": "boolean"
                            },
                            "severity": {
                                "type": "string"
                            }
                        }
                    },
                    "self_harm": {
                        "type": "object",
                        "properties": {
                            "filtered": {
                                "type": "boolean"
                            },
                            "severity": {
                                "type": "string"
                            }
                        }
                    },
                    "sexual": {
                        "type": "object",
                        "properties": {
                            "filtered": {
                                "type": "boolean"
                            },
                            "severity": {
                                "type": "string"
                            }
                        }
                    },
                    "violence": {
                        "type": "object",
                        "properties": {
                            "filtered": {
                                "type": "boolean"
                            },
                            "severity": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        }
    }
  6. HTTP (Call Text2Speech Service) アクションの構成

    25

    方法 URI ヘッダー 本文
    POST https://japaneast.tts.speech.microsoft.com/cognitiveservices/v1 Ocp-Apim-Subscription-Key:Sppech Service KEY
    Content-type:application/ssml+xml
    X-microsoft-OutputFormat:audio-16khz-128kbitrate-mono-mp3
    以下を参照
    <speak version='1.0' xml:lang='ja-JP'><voice xml:lang='ja-JP' xml:gender='Female'
        name='ja-JP-NanamiNeural'>
            @{body('Parse_Content')?['message']?['content']}
    </voice></speak>
  7. 作成 (Create Audio Media) アクションの構成

    26

    入力
    data:audio/mpeg;base64, body('Call_Text2Speech_Service')?['$content']
  8. PowerApp または Flow に応答する アクションの構成

    27

    形式 名前
    文字列 text body('Parse_Content')?['message']?['content']
    文字列 text outputs('Create_Audio_Media')

いいえの場合

  1. HTTP (Call Text2Speech for chatGPT) アクションの構成

    28

    方法 URI ヘッダー 本文
    POST https://japaneast.tts.speech.microsoft.com/cognitiveservices/v1 Ocp-Apim-Subscription-Key:Speech Service KEY
    Content-type:application/ssml+xml
    X-microsoft-OutputFormat:audio-16khz-128kbitrate-mono-mp3
    以下を参照
    <speak version='1.0' xml:lang='ja-JP'><voice xml:lang='ja-JP' xml:gender='Male'
        name='ja-JP-DaichiNeural'>
            @{body('Parse_argument_item')?['message']?['content']}
    </voice></speak>
  2. 作成 (Create Audio for chatGPT) アクションの構成

    29

    入力
    data:audio/mpeg;base64, body('Call_Text2Speech_for_chatGPT')?['$content']
  3. PowerApp または Flow に応答する アクションの構成

    30

    形式 名前
    文字列 text body('Parse_argumen_item')?['message']?['content']
    文字列 text outputs('Create_Audio_for_chatGPT')

動作確認

  1. こちらを参考に Power Apps 上で作成した Power Automate フローを関連付けます。

  2. Power Apps から質問を投げかけます。

  3. 検索対象が社内ドキュメントであれば女性の声、その他であれば男性の声で応答があれば動作確認成功です。

About

音声入力で Azure OpenAI Service の Function Calling を使って文脈に基づいた Microsoft Graph Search API 検索結果を音声で応答します。

Resources

Stars

Watchers

Forks

Languages