Delphi で gRPC を使う Google Vertex AI

· コンポーネント

Vertex AI は Google Cloud 上の Google の生成 AI プラットフォームです。gRPC の PredictionService を通じて Gemini モデルを公開しており、GenerateContent 呼び出しがプロンプトを送信してモデルの回答を返します。sgcWebSockets Enterprise には TsgcGRPCClient の上に配置される型付き Vertex AI gRPC クライアントが付属しているため、外部ランタイムなしで、Protocol Buffers を手作業で組み立てることもなく、Delphi や C++Builder から gRPC 経由で直接 Gemini を呼び出せます。

仕組み

gRPC は HTTP/2 上でフレーミングされた Protocol Buffers メッセージなので、Vertex AI 呼び出しはリージョンの aiplatform.googleapis.com エンドポイントへの HTTP/2 リクエストです。トランスポートは、ポート 443 で TLS を有効にしてリージョンホストに向けた TsgcHTTP2Client で、その上で TsgcGRPCClient がフレーミング、ヘッダー、タイムアウト、トレーラーを処理します。

Vertex AI ヘルパーは、sgcGRPC_Google_VertexAI 内の型付きメッセージクラスのセットです。コードで TsgcGRPCVertexAIGenerateContentRequest を構築し、ToBytes でシリアライズし、そのバイト列を gRPC クライアントに渡し、レスポンスのバイト列を TsgcGRPCVertexAIGenerateContentResponse に読み戻します。リクエストは Vertex AI のスキーマを反映しています。リクエストはモデルリソース名とコンテンツのリストを持ち、各コンテンツはロールと 1 つ以上のパートを持ち、パートはテキストかインラインのバイナリデータのいずれかを保持します。

Vertex AI は Google Cloud サービスアカウント認証を使用します。クライアントはサービスアカウント JWT に署名し、それを gRPC メタデータの authorization: Bearer ヘッダーとして送信するので、すべての呼び出しがリージョンの Vertex AI エンドポイントに対して認証されます。

サービスアカウントによる認証

Google Cloud クライアントは、サービスアカウント JSON キーから自己署名のサービスアカウント JWT に署名します。その JWT はオーディエンスにバインドされるため、リージョンの Vertex AI エンドポイントを対象にする必要があります。そうしないと aiplatform.googleapis.comUNAUTHENTICATED で拒否します。トークンが取得されたら、それを gRPC クライアントの DefaultMetadata に追加すると、すべての呼び出しに付随します。

uses
  sgcHTTP_Google_Cloud, sgcGRPC_Client;

// CloudClient is a TsgcHTTPGoogleCloud_PubSub_Client used here only to sign the JWT
CloudClient.GoogleCloudOptions.Authentication := gcaJWT;
CloudClient.GoogleCloudOptions.JWT.ClientEmail   := ClientEmail;
CloudClient.GoogleCloudOptions.JWT.PrivateKeyId  := PrivateKeyId;
CloudClient.GoogleCloudOptions.JWT.PrivateKey.Text := PrivateKey;
CloudClient.GoogleCloudOptions.JWT.ProjectId     := ProjectId;
CloudClient.GoogleCloudOptions.JWT.API_Endpoint  :=
  'https://' + Region + '-aiplatform.googleapis.com/';

// after the token arrives in OnAuthToken
GRPC.DefaultMetadata.Clear;
GRPC.DefaultMetadata.Add('authorization', 'Bearer ' + Token);

トランスポートのセットアップ

リージョンホスト用の TsgcHTTP2Client を作成し、それを gRPC クライアントに割り当てます。Vertex AI は application/grpc+proto を話すので、チャネルのコンテンツタイプは grpcProto のままにします。

uses
  sgcHTTP2_Client, sgcGRPC_Client, sgcGRPC_Types;

HTTP2 := TsgcHTTP2Client.Create(nil);
HTTP2.Host := Region + '-aiplatform.googleapis.com';   // e.g. us-central1-...
HTTP2.Port := 443;
HTTP2.TLS  := True;

GRPC := TsgcGRPCClient.Create(nil);
GRPC.Client := HTTP2;
GRPC.ChannelOptions.ContentType := grpcProto;
GRPC.ChannelOptions.Compression := grpcNoCompression;

HTTP2.Active := True;

プロンプトからのコンテンツ生成

型付きメッセージクラスからリクエストを構築します。モデルは完全なリソース名で、唯一のコンテンツは user ロールを持ち、プロンプトはテキストパートに入ります。Call はレスポンスが届くまでブロックし、生の Data バイト列を持つ TsgcGRPCResponse を返します。これを型付きレスポンスに読み込みます。

uses
  sgcGRPC_Client, sgcGRPC_Types, sgcGRPC_Google_VertexAI;

var
  oRequest: TsgcGRPCVertexAIGenerateContentRequest;
  oContent: TsgcGRPCVertexAIContent;
  oPart: TsgcGRPCVertexAIPart;
  oResponse: TsgcGRPCResponse;
begin
  oRequest := TsgcGRPCVertexAIGenerateContentRequest.Create;
  try
    oRequest.Model := 'projects/' + ProjectId + '/locations/' + Region +
      '/publishers/google/models/' + Model;   // e.g. gemini-2.0-flash

    oContent := oRequest.AddContent;
    oContent.Role := 'user';
    oPart := oContent.AddPart;
    oPart.Text := 'Explain gRPC in one sentence.';

    oResponse := GRPC.Call(
      'google.cloud.aiplatform.v1.PredictionService', 'GenerateContent',
      oRequest.ToBytes);

    if oResponse.StatusCode = grpcOK then
      ParseResponse(oResponse.Data)
    else
      ShowMessage('gRPC error: ' + oResponse.StatusMessage);
  finally
    oRequest.Free;
  end;
end;

レスポンスの読み取り

レスポンスのバイト列を TsgcGRPCVertexAIGenerateContentResponse に読み込みます。これは候補を公開し、各候補はそのコンテンツパートと終了理由を持ち、加えてプロンプト、候補、合計のトークン数を持つ UsageMetadata ブロックも公開します。

procedure ParseResponse(const aData: TBytes);
var
  oResponse: TsgcGRPCVertexAIGenerateContentResponse;
  oCandidate: TsgcGRPCVertexAICandidate;
  i, j: Integer;
begin
  oResponse := TsgcGRPCVertexAIGenerateContentResponse.Create;
  try
    oResponse.LoadFromBytes(aData);
    for i := 0 to oResponse.CandidateCount - 1 do
    begin
      oCandidate := oResponse.Candidate(i);
      for j := 0 to oCandidate.Content.PartCount - 1 do
        Memo1.Lines.Add(oCandidate.Content.Part(j).Text);
    end;
    Memo1.Lines.Add('Total tokens: ' +
      IntToStr(oResponse.UsageMetadata.TotalTokenCount));
  finally
    oResponse.Free;
  end;
end;

生成設定と安全性設定

リクエストは、省略可能な GenerationConfig と安全性設定のリストも持ちます。サンプリングと長さを制御するには設定を使います。TemperatureTopPTopKCandidateCountMaxOutputTokensStopSequences です。各安全性設定は、危害の Category とブロックする Threshold を対にします。

oRequest.GenerationConfig.Temperature     := 0.7;
oRequest.GenerationConfig.MaxOutputTokens := 1024;
oRequest.GenerationConfig.StopSequences.Add('END');

with oRequest.AddSafetySetting do
begin
  Category  := 7;   // HARM_CATEGORY_DANGEROUS_CONTENT
  Threshold := 2;   // BLOCK_MEDIUM_AND_ABOVE
end;

ストリーミングレスポンス

Vertex AI は StreamGenerateContent も公開しています。これはサーバーストリーミングの対応版で、回答を 1 つのブロックではなく一連の部分チャンクとして返します。Vertex AI ヘルパーは TsgcGRPCClient の上に構築されているため、同じ型付きリクエストがクライアントのサーバーストリーミング API に供給されます。呼び出しを開始し、到着するたびに各チャンクを TsgcGRPCVertexAIGenerateContentResponse にデコードし、その候補からテキストを追記して、モデルが書き進めるにつれて UI を更新します。

提供状況

Vertex AI gRPC クライアントは sgcWebSockets Enterprise エディションの一部です。すぐに実行できるサンプルは Demos\21.GRPC\17.Vertex_AI にあります。サービスアカウント JSON キーを貼り付けるか読み込み、プロジェクト、リージョン、モデルを設定し、接続してから、Generate Content でプロンプトを送信します。完全なリファレンスは gRPC Client 製品ページにあります。

ご質問やご意見はありますか? お問い合わせください。コードを書いた本人から返信いたします。