少し前ですが、Azure FunctionsのNode.jsでstream対応になりました。
stream対応したということは、OpenAIのstreamの返信をFunctions経由で返すことができるということなので、このブログで検証しようと思います。
※OpenAIのAPIを使います。AzureOpenAIではないです。
環境
@azure/functions 4.0.0
openai 4.38.1
FunctionsはHTTPトリガーを使用
Functions実装
最初に環境変数を設定します。
名前:"OpenAIApiKey"
値:OpenAIのAPIキー
local.setting.jsonに記載する場合は以下のようになります。
{ "Values": { "OpenAIApiKey": "[OpenAIのAPIキー]" } }
ソースコードを書いていきます。
まずOpenAIのパッケージをインストールします。
npm install openai
コードを書いていきます。
まず以下でFunctionsのstreamを有効にします。
app.setup({ enableHttpStream: true });
APIキーを環境変数から取得してOpenAIのクライアントを作成します。
const apiKey = process.env["OpenAIApiKey"]; const openai = new OpenAI({ apiKey: apiKey});
HTTPリクエストを受けてreturnする関数は以下のようにします。
async function httpTrigger1(req: HttpRequest){ //本文から値を取得 //想定しているJSONは以下の通り //{"prompt": "Hello, World!"} const prompt = req.params['prompt']; //OpenAIのAPIを呼び出す const completion = await openai.chat.completions.create({ messages: [{ role: "user", content: prompt }], model: "gpt-3.5-turbo", stream: true }); //streamを生成するgenerator const generator = async function* () { for await (const chunk of completion) { if(chunk.choices[0].finish_reason === "stop"){ break; } yield chunk.choices[0].delta.content + '\n'; } }; //streamを取得 const stream = Readable.from(generator()); //streamを返す return { body: stream, headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-store' } }; };
ポイントとしては以下の通りです。
- OpenAIにリクエストを送るときは
stream: true
にします レスポンスのヘッダーに
'Content-Type': 'text/event-stream','Cache-Control': 'no-store'
を設定します
参考: developer.mozilla.orggeneratorのあたりはあまりよくわかっていませんが、見よう見まねで実装しました
全体のコードは以下のようになります。
import { app, HttpRequest } from "@azure/functions"; import OpenAI from "openai"; import { Readable } from "stream"; //streamを使うために必要 app.setup({ enableHttpStream: true }); // APIキーを環境変数から取得 const apiKey = process.env["OpenAIApiKey"]; const openai = new OpenAI({ apiKey: apiKey}); async function httpTrigger1(req: HttpRequest){ //本文から値を取得 //想定しているJSONは以下の通り //{"prompt": "Hello, World!"} const prompt = req.params['prompt']; //OpenAIのAPIを呼び出す const completion = await openai.chat.completions.create({ messages: [{ role: "user", content: prompt }], model: "gpt-3.5-turbo", stream: true }); //streamを生成するgenerator const generator = async function* () { for await (const chunk of completion) { if(chunk.choices[0].finish_reason === "stop"){ break; } yield chunk.choices[0].delta.content + '\n'; } }; //streamを取得 const stream = Readable.from(generator()); //streamを返す return { body: stream, headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-store' } }; }; app.http('httpTrigger1', { methods: ['POST'], authLevel: 'function', handler: httpTrigger1 });
Functions側はこれで完了です。
クライアントの実装
次にFunctionsにアクセスするクライアントをC#のコンソールアプリで実装します。
using System.Text; using System.Text.Json; var endpointUrl = "[FunctionsのURL]"; var promptString = "[OpenAIに送信するプロンプト]"; //HttpClientの作成 var client = new HttpClient(); //HTTPリクエストの作成 var request = new HttpRequestMessage(HttpMethod.Post, endpointUrl); //JSON形式でデータを送信 using StringContent jsonContent = new( JsonSerializer.Serialize(new { prompt = promptString }), Encoding.UTF8, "application/json"); request.Content = jsonContent; //リクエストの送信 var response = await client.SendAsync(request,HttpCompletionOption.ResponseHeadersRead); //レスポンスの読み取って表示 using var stream = await response.Content.ReadAsStreamAsync(); using var streamReader = new StreamReader(stream); while (!streamReader.EndOfStream) { var line = await streamReader.ReadLineAsync(); if (string.IsNullOrEmpty(line)) continue; Console.Write(line); }
promptStringにこんにちは。お元気ですか?
と設定してプログラムを実行すると以下のようになります。
想定通り少しずつ回答を取得して表示していますね!
参考URL
Azure Functions の Node.js v4 に追加された HTTP Streaming サポートを深掘りする - しばやん雑記
https://platform.openai.com/docs/api-reference/chat/create?lang=node.js