はじめに
最近話題となっているChatGPTですが、この記事を書いた時点ではWebブラウザからの利用しかできません。
ChatGPTの開発元であるOpenAIからはChatGPTのように文章を理解してくれるAPIが公開されています。
APIとAzure Cognitive Searchを連携して、ChatGPTのようにテキストファイルを要約できるカスタムスキルを作成できるのか試してみました。
作業概要
下記の手順で実施します。
- OpenAIのAPIキーを取得する。
- ストレージアカウントを作成する。
- 要約したいテキストファイルをストレージアカウントのコンテナーに保存する。
- Azure Cognitive Searchを作成する。
- Azure Cognitive Searchのデータのインポートを行う。
- Azure Functionsを作成する。
- カスタムスキルを作成する。
作業手順
OpenAIのAPIキーを取得する
OpenAIの下記サイトにログインし、APIキーを取得します。
https://platform.openai.com/account/api-keys

ストレージアカウントを作成する
要約したいテキストファイルを保存する為のストレージアカウントをAzureに作成します。

要約したいテキストファイルをストレージアカウントのコンテナーに保存する
今回は text という名称のコンテナーを作成してテキストファイルを保存しました。


保存したテキストファイル "Windows10について.txt" の内容は下記です。
ChatGPTに「Windows10について説明して下さい」と質問した際の回答を保存しました。

Azure Cognitive Searchを作成する
Azure Cognitive SearchをAzureに作成します。

Azure Cognitive Searchのデータのインポートを行う
Azure Cognitive Searchのデータのインポートを選択し、テキストファイルや要約の内容を保存するインデックス、インデクサー、データソース、スキルセットを作成します。

データソースは Azure BLOBストレージ を選択します。

データソース名は任意の名称を設定します。
接続文字列の箇所にある”既存の接続を選択します”を選択し、上記で作成したストレージアカウントとコンテナを選択します。

画面左下にある "次:コグニティブ スキルを追加します" を選択します。

下記画面の"Cognitive Serviceをアタッチする"の箇所で、"無料(制限付きのエンリッチメント)"を選択します。
スキルセット名は任意の名称を設定します。
今回はデータが保存できればよいので、"テキストの認知技術"の箇所は組織名とキーフレーズの抽出だけチェックを入れました。
画面下にある"次:対象インデックスをカスタマイズします"を選択します。

インデックス名は任意の名称を設定します。
下記画面の”フィールドの追加”を選択し、文章を要約した結果を保存する為のフィールドを作成します。
今回はフィールド名は summary"、フィールドの型はEdm.Stringで作成しました。
"取得可能"と"検索可能"にチェックを入れて、アナライザーには"日本語 Microsoft"を選択しました。
画面下にある"次:インデクサーの作成"を選択します。

インデクサーの名称を任意に設定します。

画面下にある送信を選択するとインデクサーが実行されます。
※Azure Cognitive Searchにインデックスが作成されます。

Azure Cognitive Searchに作成されたインデックスの内容を検索して確認します。

ストレージアカウントに保存したテキストファイルの内容がインデックスとして保存されました。

Azure Functionsを作成する
Azure FunctionsをAzureに作成します。

Visual Studioなどを利用して、下記のソースコードをAzure Functionsにデプロイします。
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
namespace SampleSkills
{
public static class OpenAi
{
private class InputRecord
{
public class InputRecordData
{
public string Text { get; set; }
}
public string RecordId { get; set; }
public InputRecordData Data { get; set; }
}
private class WebApiRequest
{
public List<InputRecord> Values { get; set; }
}
private class OutputRecord
{
public class OutputRecordData
{
public string Text { get; set; } = "";
}
public class OutputRecordMessage
{
public string Message { get; set; }
}
public string RecordId { get; set; }
public OutputRecordData Data { get; set; }
public List<OutputRecordMessage> Errors { get; set; }
public List<OutputRecordMessage> Warnings { get; set; }
}
private class WebApiResponse
{
public List<OutputRecord> Values { get; set; }
}
[FunctionName("OpenAi")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("function: C# HTTP trigger function processed a request.");
var response = new WebApiResponse
{
Values = new List<OutputRecord>()
};
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var data = JsonConvert.DeserializeObject<WebApiRequest>(requestBody);
// Do some schema validation
if (data == null)
{
return new BadRequestObjectResult("The request schema does not match expected schema.");
}
if (data.Values == null)
{
return new BadRequestObjectResult("The request schema does not match expected schema. Could not find values array.");
}
// Calculate the response for each value.
foreach (var record in data.Values)
{
if (record == null || record.RecordId == null) continue;
OutputRecord responseRecord = new OutputRecord
{
RecordId = record.RecordId
};
try
{
responseRecord.Data = await GetOpenAiData(record.Data.Text);
}
catch (Exception e)
{
// Something bad happened, log the issue.
var error = new OutputRecord.OutputRecordMessage
{
Message = e.Message
};
responseRecord.Errors = new List<OutputRecord.OutputRecordMessage>
{
error
};
}
finally
{
response.Values.Add(responseRecord);
}
}
return (ActionResult)new OkObjectResult(response);
}
private async static Task<OutputRecord.OutputRecordData> GetOpenAiData(string text)
{
var result = new OutputRecord.OutputRecordData();
//OpenAI Url
var apiUrl = "https://api.openai.com/v1/engines/text-davinci-003/completions";
// OpenAI API key
var apiKey = "**************************************";
string question = "次の文章を要約して下さい。" + text;
// Request body
var requestBody = new
{
prompt = question,
temperature = 0.5,
max_tokens = 300
};
// Serialize request body
var json = JsonConvert.SerializeObject(requestBody);
// Create the request
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(apiUrl),
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
// Add the API key to the headers
request.Headers.Add("Authorization", $"Bearer {apiKey}");
// Send the request
var responseContent = "";
JObject json_object;
using (var client = new HttpClient())
{
var response = await client.SendAsync(request);
responseContent = await response.Content.ReadAsStringAsync();
json_object = JObject.Parse(responseContent);
result.Text = (string)json_object["choices"][0]["text"];
}
return result;
}
}
}
OpenAIのAPIは下記を指定しています。
OpenAIから取得したAPIキーは下記の箇所に記載します。
下記の文章をAPIで送信して、テキストデータを要約します。
※変数textには、Azure Functionsが受信したテキストデータが格納されています。
デプロイが完了したら、Azure Functionsのurlをコピーします。
※カスタムスキルの作成に使用します。

カスタムスキルを作成する
Azure Cognitive Searchの"デバッグセッションを追加"を選択します。

デバッグセッション名は任意の名称を設定します。
ストレージ接続文字列の"既存の接続を選択します"を選択し、デバッグセッションのファイルを保存するコンテナーを設定します。
インデクサーテンプレートの箇所で、カスタムスキルを追加するインデクサーを選択します。
左上にある"セッションの保存"を選択します。

デバッグセッションの内容が表示されます。
デバッグセッションを使用すると、Azure Cognitive Searchのスキルセットに設定した内容を画面で確認することができます。
※データのインポートを行った時にチェックした、組織名とキーフレーズを抽出するスキルが表示されます。

画面右側に表示されている"新しいスキルの追加"を選択します。

カスタムスキルに下記の内容を設定します。
技能の箇所で"カスタムWeb APIスキル"を選択します。
"name"には任意の名称を設定します。
"url"には、作成したAzure Functionsのurlを設定します。
左上にある"保存"を選択します。
カスタムスキルが追加されます。
※NEWと表示されているのが、追加されたカスタムスキルです。
カスタムスキルが処理した内容をインデックスに格納する為に、"出力フィールドのマッピング"を選択します。

ソースフィールドに /document/content/summary を設定します。
ターゲットフィールドに summary を設定します。
左上にある"保存"を選択します。

デバッグセッションの"実行"を選択します。
※ストレージアカウントに保存されたテキストデータを読み込んで、カスタムスキルが実行されます。

カスタムスキルが正常に動作すれば、下記の画面が表示されます。

"エンリッチ処理されたデータ構造"を選択すると、スキルセットが取得したテキストデータやインデックスに保存する内容を確認する事ができます。

contentには、ストレージアカウントから取得したテキストデータが格納されています。

summaryには、カスタムスキルにより要約された内容が格納されています。

"変更をコミットする"を選択し、作成したカスタムスキルをスキルセットに追加します。
次回からはインデクサー実行時に、カスタムスキルが実行されます。

まとめ
Azure Cognitive SearchとOpenAIのAPIを連携するカスタムスキルを作成することができました。
要約については、ChatGPTの方がわかりやすい回答でした。

今回は「文章を要約して下さい」と指示を出しましたが、「翻訳して下さい」と指示を出せば翻訳してくれます。
「〇〇して下さい」の〇〇の部分を置き換えれば、様々な文章の変換が可能です。
日本語ではなく、英語で問い合わせれば異なる結果が得られるかもしれません。
今回作成したカスタムスキルを使用すれば、ChatGPTのAPIが公開された場合も対応できます。
Azure OpenAI ServiceでもChatGPTのAPIは公開される予定です。
Azure Cognitive Searchと連携して使用すれば、変換したデータを保存して高速で検索できるので大変便利です。
3月8日追記:出力フィールドのマッピングと、ソースコードの一部に誤りがあったので修正しました。