🎨

Radzen.BlazorのRadzenAIChatコンポーネントでAIと会話してみる

に公開2

はじめに

Blazorを使用した開発に携わる皆様、UIフレームワークは何を使用されてますでしょうか。

私はRadzenを好んで使用します。
いまサラっと「好んで」と綺麗な言葉を使いましたが、正直に言うと、私がBlazorのUIフレームワークを選ぶ際はほとんどRadzenを使用しています。というかまずRadzenで何とかならないか、などと考えます。
そんな自称 Radzen 親善大使な私ですが、いつのまにやらRadzenからRadzenAIChatコンポーネントなるものがリリースされていることを知りました。
Radzen 親善大使としては使用してみる他ありません。
そんなこんなでRadzenへの日頃の感謝も込めて、本記事の作成に至ります。

Radzen Blazor とは

https://blazor.radzen.com/?theme=material3

Radzen Blazorは、DataGrid、スケジューラ、チャート、およびマテリアルデザインの堅牢なテーマが満載の90 +無料のネイティブBlazorUIコンポーネントのセットです。

BlazorのUIフレームワークは他にも多々ありますが、Radzenは公式ページにサンプルが豊富で、かつブラウザページ上のEditSoureにてコンポーネントのちょっとした変更をその場で試せる点が素晴らしいです。

RadzenAIChat コンポーネント とは

https://blazor.radzen.com/aichat?theme=material3

RadzenAIChatコンポーネントは、BlazorアプリにChatGPT風の会話UIを簡単に組み込めるコンポーネントです。OpenAIやAzure OpenAIなど任意のAIエンドポイントと接続でき、会話履歴の保持、イベントフック、スタイルのカスタマイズなどが可能です。

RadzenAIChatコンポーネント、かなり手軽にチャットUIを作れるようです。イベントや見た目もそれなりに選択肢があり、自分のアプリに合わせてサクッとカスタマイズできそうです。

環境

開発環境

  • Visual Studio 2022 IDE

https://visualstudio.microsoft.com/ja/vs/

  • .NET 9 SDK

https://dotnet.microsoft.com/ja-jp/download/dotnet/9.0

  • Azure OpenAI
    Azure AI Foundryより接続可能なモデルを事前に準備している前提です。
    本記事では以下のモデルを使用しています。
    • モデル名:gpt-4o-mini
    • API Version:2024-02-15-preview

https://ai.azure.com/?cid=learnDocs

https://learn.microsoft.com/ja-jp/azure/ai-foundry/what-is-azure-ai-foundry

NuGet

  • Radzen (本記事掲載時はv 7.4.2)

https://www.nuget.org/packages/Radzen.Blazor/

構築するアプリ

クライアントはRadzenを使用したBlazor WASM、サーバーはWebAPI(ASP.NET Core)で構築を行います。
以下は簡単なアーキテクチャ図です。

※Web APIが必要な理由は後述します。

実装

いざいざRadzenAIChatコンポーネントを実装します。

まずは以下2つのプロジェクトをソリューション内に作成します。

  • Blazor WASM プロジェクト(BlazorAppRadzenPoC)
  • WebAPI プロジェクト(ChatProxyAPI)

BlazorAppRadzenPoCプロジェクトには、AIChat.razorページを追加しており、NavMenu.razorから遷移可能としている前提です。

Radzenを使うためのコード上での設定手順は割愛します。
以下のQiita記事がわかりやすかったので、引用ご紹介とさせていただきます。

  • Qiita Blazor向けのUIフレームワークのRadzen.Blazorを使ってみる

https://qiita.com/nobu17/items/b6fb2e35f4943a1d325a

クライアント側

BlazorAppRadzenPoCプロジェクトのAIChat.razorにRadzenAIChatコンポーネントを実装します。

以下のようにAIChat.razorページにRadzenAIChatコンポーネントを配置します。
様々なParameterがありますが今回はあまり指定せず、ほとんどデフォルトの設定で配置しています。(面倒でしたので。。)
詳細はRadzenの以下ドキュメントページやRadzenのソースコードを参照ください。

https://blazor.radzen.com/docs/api/Radzen.AIChatService.html#properties

AIChat.razor
@page "/aichat"
@using Radzen.Blazor

<PageTitle>AI Chat</PageTitle>

<RadzenAIChat SessionId="@sessionId"
              Title="AI Chat"
              Placeholder="メッセージを入力してください..."
              Style="height: 600px;" />

@code {
    private string sessionId = Guid.NewGuid().ToString();
}

また、コンポーネント配置の他にRadzenServiceのDI注入が必要です。
以下のようにRadzen.AddAIChatServiceメソッドを使用して、Blazor WASM側のProgram.csにAIChatServiceの設定を追加します。

Program.cs(BlazorAppRadzenPoC)
using BlazorAppRadzenPoC;
using BlazorAppRadzenPoC.Configuration;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Radzen;

WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

// HttpClient を設定
builder.Services.AddScoped(sp => new HttpClient());

// Radzen Services
builder.Services.AddScoped<DialogService>();
builder.Services.AddScoped<NotificationService>();
builder.Services.AddScoped<TooltipService>();
builder.Services.AddScoped<ContextMenuService>();

// RadzenのAIChatServiceの設定を追加
// 各値は仮値です。適宜編集ください。
builder.Services.AddAIChatService(options =>
{
    options.Proxy = "https://localhost:7105/api/chat/completions";
    options.Model = "gpt-4o-mini";
    options.SystemPrompt = "あなたは親切で有用なAIアシスタントです。";
    options.Temperature = 0.7;
    options.MaxTokens = 2000;
});

await builder.Build().RunAsync();

クライアント側の実装は以上です。簡単ですね。

サーバー側

ChatProxyAPIプロジェクト(WebAPI)に、Azure OpenAIに接続しAIよりレスポンスを受信と返却を行う機能を実装します。

ポイントとして、クライアント側のRadzen.AIChatServiceがAIから応答を得るときに実行されるGetCompletionsAsyncメソッドは、Streamでレスポンスを受信する前提で実装されているようです。

https://blazor.radzen.com/docs/api/Radzen.AIChatService.html#methods

ですので、サーバー側APIの処理もStreamでreturnするように実装する必要があります。

以下はサーバー側WebAPI機能をMinimalAPIで実装した例です。

Program.cs(ChatProxyAPI)
// ASP.NET Core Web API アプリケーションのビルダーを作成
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// CORS(Cross-Origin Resource Sharing)設定
// Blazor WebAssembly アプリケーションからのクロスオリジンリクエストを許可
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        // 任意のオリジン(ドメイン)からのアクセスを許可
        _ = policy.AllowAnyOrigin()
              // 任意のHTTPメソッド(GET, POST, PUT, DELETE等)を許可
              .AllowAnyMethod()
              // 任意のHTTPヘッダーを許可
              .AllowAnyHeader();
    });
});

WebApplication app = builder.Build();
app.UseHttpsRedirection();

// CORSミドルウェアを有効化
// 上記で設定したCORSポリシーを適用
app.UseCors();

// RadzenAIChatService用のOpenAI互換プロキシエンドポイントをMinimal APIで実装
// POST /api/chat/completions とエンドポイントを定義
// RadzenのAIChatServiceからのリクエストをAzure OpenAI APIにプロキシ転送
app.MapPost("/api/chat/completions", async (HttpContext context, ILogger<Program> logger) =>
{
    try
    {
        // Azure OpenAI接続情報をセット。適宜設定してください。
        // Azure OpenAI リソースのエンドポイントURL
        string? endpoint = "https://XXX.openai.azure.com/";
        // デプロイメント名(モデル名)
        string? deploymentName = "gpt-4o-mini など";
        // APIキー
        string? apiKey = "XXX";
        // API Version
        string? apiVersion = "20XX-XX-XX-previewなど"

        // Azure OpenAI Chat Completions API のエンドポイントURL構築
        string azureOpenAIUrl = $"{endpoint}openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}";

        // プロキシリクエストメッセージを作成
        // 元のHTTPリクエストボディをそのままAzure OpenAI APIに転送
        HttpRequestMessage request = new(HttpMethod.Post, azureOpenAIUrl)
        {
            // リクエストボディをストリームとして設定(メモリ効率的)
            Content = new StreamContent(context.Request.Body)
        };

        // Content-Typeヘッダーの設定
        // 通常は "application/json" だが、charset情報などを適切に処理
        if (context.Request.ContentType != null)
        {
            // Content-Type から メディアタイプのみを抽出(charset部分を除去)
            string mediaType = context.Request.ContentType.Split(';')[0].Trim();
            request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mediaType);
        }

        // Azure OpenAI認証用のAPIキーヘッダーを追加
        // Azure OpenAI では "api-key" ヘッダーで認証を行う
        request.Headers.Add("api-key", apiKey);

        // HttpClientを使用してAzure OpenAI APIにリクエストを送信
        using HttpClient httpClient = new();
        HttpResponseMessage response = await httpClient.SendAsync(request);

        // クライアントに返すレスポンスのステータスコードを設定
        // Azure OpenAI APIからのステータスコードをそのまま転送
        context.Response.StatusCode = (int)response.StatusCode;

        // レスポンスヘッダーをクライアントにコピー
        // Azure OpenAI APIからのヘッダーを適切に転送
        foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers.Concat(response.Content.Headers))
        {
            // "transfer-encoding" ヘッダーは除外
            // ASP.NET Core が自動的に処理するため、手動設定すると競合する
            if (header.Key != "transfer-encoding")
            {
                context.Response.Headers[header.Key] = header.Value.ToArray();
            }
        }

        // Azure OpenAI APIからのレスポンスボディをストリーミングで転送
        // メモリ効率的にデータを転送し、大きなレスポンスでもメモリ不足を防ぐ
        return Results.Stream(
            // レスポンスストリーム
            await response.Content.ReadAsStreamAsync(),
            // Content-Type(デフォルト: application/json)
            response.Content.Headers.ContentType?.ToString() ?? "application/json" 
        );
    }
    catch (Exception ex)
    {
        return Results.Problem(ex.Message, statusCode: 500);
    }
});

app.Run();

サーバー側の実装としては以上です。
クライアント側からhttpClinetを使用して直接Auzre OpenAI へ接続することも可能と考えますが、APIキーがブラウザ側に渡ってしまうなど、セキュリティの関係上、上記のように仲介するWebAPIを準備するアプローチが推奨されます。

動作確認

いよいよ動作確認を行います。

左側がWebAPI、右側がクライアントです。
gif

無事、AIの回答結果がクライアント側に表示されました。正直、泣いた。

まとめ

ということで、RadzenのAIChatコンポーネントを簡単に実装してみました。

RadzenAIChatコンポーネントを使えば、わずかなコードでChatGPT風の会話UIをBlazorアプリに組み込むことができます。
RadzenAIChatコンポーネントの配置とDI設定だけで基本的な機能が使え、プロキシAPIを通すことでセキュアにAzure OpenAIと連携できます。
Radzenらしく直感的で使いやすく、AIチャット機能を手軽に実装したい場面では非常に有用なコンポーネントだと感じます。

本記事の内容が何かしらのお役に立てば幸いです。

参考

  • GitHub Radzen Blazor

https://github.com/radzenhq/radzen-blazor

  • Radzen Blog

https://radzen.com/blog

Discussion

オオカミさんオオカミさん

私は基本はDevExpress です、PDFを扱う場合はSyncfusionになります。
SyncfusionのSpreadSheetは、徳に凄くて、ほぼ、Excelです。

tomotomo

コメントいただきありがとうございます!
DevExpress は少し触ったことがあり堅実な印象でよかったです。Syncfusionは触ったことなかったので、情報提供うれしいです!少し調べましたがSpreadSheetはおっしゃるようにExcelさながらのようですね。需要が高そうです。今度、触ってみます!ありがとうございます。