GeminiAPIWrapper

はじめに

Google の Gemini API を .NET アプリケーションから利用する際、REST API を直接呼び出すのは意外と面倒な作業です。エンドポイントの構築、リクエストの組み立て、レスポンスのパース、エラーハンドリングなど結構手間がかかります。

そこで開発したのが GeminiAPIWrapper です。このライブラリは Generative Language API へのアクセスを簡潔にし、わずか数行のコードで Gemini の強力な AI 機能を .NET アプリケーションに組み込めるようにします。

※このライブラリを作成している間にdotnet-genaiという同じ思想のライブラリがリリースされました(笑) でもこのライブラリでしかできないこともあるので見ていってください。

主な特徴:

  • シンプルで直感的な API 設計
  • ストリーミング・非ストリーミング両方の応答に対応
  • System.Text.Json のソース生成による高速なシリアライゼーション
  • .NET 9 以降をサポート
  • API キーヘッダーのカスタマイズが可能

この記事では、GeminiAPIWrapper の基本的な使い方から、より高度な機能まで詳しく解説していきます。

インストール

NuGetパッケージマネージャを使うか、以下のコマンドでインストールできます。

dotnet add package GeminiAPIWrapper

クイックスタート

まずは最もシンプルな使い方から見ていきましょう。

using GeminiAPIWrapper;
using GeminiAPIWrapper.Options;
using GeminiAPIWrapper.Extensions;

// オプションを設定
var options = new GenerativeLanguageOptions
{
    EndpointBase = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash",
    ApiKey = "YOUR_API_KEY"
};

// サービスを構築
var service = GeminiServiceBuilder.Build(options);

// テキスト生成
var response = await service.GenerateAsync("こんにちは");
Console.WriteLine(response?.GetText());

重要: EndpointBase には :generateContent:streamGenerateContent などの操作名を含めないでください。 ライブラリが自動的に適切な操作名を付加します。

ストリーミング応答

長文の生成やリアルタイムな応答表示が必要な場合は、ストリーミングAPIを使用します。await foreach を使って、生成されたテキストを逐次的に受け取ることができます。

await foreach (var chunk in service.StreamGenerateAsync("長文を生成してください"))
{
    Console.Write(chunk?.GetText());
}

これにより、ユーザーは待ち時間を感じることなく、生成されたテキストを順次確認できます。

詳細な設定を行う場合

生成される応答の品質や特性をコントロールしたい場合は、GenerationConfig を使用します。

using GeminiAPIWrapper.Configurations;

var generationConfig = new GenerationConfig
{
    Temperature = 0.7,        // 創造性の度合い (0.0~2.0)
    MaxOutputTokens = 1024    // 最大出力トークン数
};

var response = await service.GenerateAsync(
    userMessage: "AIについて説明してください",
    systemInstruction: "あなたは親切なアシスタントです",
    generationConfig: generationConfig
);
  • Temperature: 値が高いほど創造的で多様な応答になり、低いほど決定論的で一貫性のある応答になります
  • MaxOutputTokens: 生成されるテキストの最大長を制御します
  • SystemInstruction: AIの振る舞いやペルソナを定義します

カスタムリクエストを使用する場合

より細かい制御が必要な場合や、複数のメッセージを含む会話を構築したい場合は、GeminiRequest を直接作成します。

using GeminiAPIWrapper;
using GeminiAPIWrapper.Configurations;

var request = new GeminiRequest
{
    Contents = 
    [
        new Content
        {
            Role = Role.User,
            Parts = [new Part { Text = "こんにちは" }]
        }
    ],
    GenerationConfig = new GenerationConfig { Temperature = 0.9 }
};

var response = await service.GenerateAsync(request);

この方法では、会話履歴を保持したり、複数ターンの対話を実現することができます。

API キーヘッダーのカスタマイズ

デフォルトでは X-Goog-Api-Key ヘッダーが使用されますが、プロキシや独自のAPI Gateway を経由する場合など、カスタムヘッダー名が必要になることがあります。

var options = new GenerativeLanguageOptions
{
    EndpointBase = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp",
    ApiKey = "YOUR_API_KEY",
    KeyHeader = "Custom-Api-Key-Header" // カスタムヘッダー名
};

これにより、企業のセキュリティポリシーに準拠したり、独自のAPI管理システムと統合することが容易になります。

主要なクラス

  • GeminiServiceBuilder: サービスインスタンスを構築するファクトリクラス
  • GeminiService: API 呼び出しを行うメインサービスクラス
  • GeminiRequest: リクエストの詳細を構成するクラス
  • GeminiResponse: API からの応答を表すクラス
  • GenerativeLanguageOptions: Google AI Studio 用の設定

拡張メソッド

GeminiResponse には便利な拡張メソッドが用意されています。

using GeminiAPIWrapper.Extensions;

// テキストを取得
string? text = response.GetText();

// Function Call を取得
FunctionCall? functionCall = response.GetFunctionCall();

ResponseSchemaの利用

Gemini API の強力な機能の一つが、応答の形式を JSON スキーマで制御できる Response Schema です。これにより、構造化されたデータを確実に取得できます。

実行例

以下は、ガレットのレシピを構造化された JSON 形式で取得する例です。

using GeminiAPIWrapper;
using GeminiAPIWrapper.Configurations;
using GeminiAPIWrapper.Extensions;
using GeminiAPIWrapper.Options;
using SampleApp;
using System.Text.Json;

var options = new GenerativeLanguageOptions() { ApiKey = "{YourAPIKey}", EndpointBase = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash" };
var service = GeminiServiceBuilder.Build(options);

// スキーマに合った質問を設定
var request = ResponseSchemaRequest.BuildRequest(
    userMessage: "ガレットの作り方を教えてください。",
    systemInstruction: "あなたはプロフェッショナルなフレンチシェフです。"
);

// 質問に対する応答スキーマを取得
var response = await service.GenerateAsync(request);
var text = response.GetText();
// 結果はJSONフォーマットで返される
// {"title":"ガレット","servings":2,"ingredients":["そば粉 100g","水 200ml",...], "steps":["ボウルにそば粉と水を入れて混ぜる",...]}

スキーマ定義クラス

スキーマの定義は別クラスで管理すると再利用性が高まります。

using GeminiAPIWrapper;
using GeminiAPIWrapper.Configurations;

namespace SampleApp;

internal class ResponseSchemaRequest
{
    private static Schema BuildResponseSchema()
    {
        return new Schema
        {
            Type = SchemaType.Object,
            Description = "ガレットの簡潔なレシピ構造。必須は料理名・材料・手順。",
            Properties = new Dictionary<string, Schema>
            {
                { "title", new Schema { Type = SchemaType.String, Description = "料理名" } },
                { "servings", new Schema { Type = SchemaType.Integer, Description = "何人分か" } },
                { "ingredients", new Schema { Type = SchemaType.Array, Description = "材料(1項目につき '材料名 分量' の短文)", Items = new Schema { Type = SchemaType.String } } },
                { "steps", new Schema { Type = SchemaType.Array, Description = "作り方の手順(短い命令文の配列)", Items = new Schema { Type = SchemaType.String } } }
            },
            Required = ["title", "ingredients", "steps"]
        };
    }

    public static GeminiRequest BuildRequest(string userMessage, string systemInstruction)
    {
        var responseSchema = BuildResponseSchema();
        return new GeminiRequest
        {
           Contents = 
           [
                new Content
                {
                    Role = Role.User,
                    Parts = 
                    [
                        new Part 
                        { 
                            Text = userMessage 
                        }
                    ],
                }
           ],
           SystemInstruction = new SystemInstruction
           {
               Parts = 
               [
                    new Part 
                    { 
                        Text = systemInstruction 
                    }
               ],
           },
           GenerationConfig = new GenerationConfig
           {
               ResponseSchema = responseSchema,
               ResponseMimeType = ResponseMimeType.ApplicationJson
           }
        };
    }
}

ポイント

  • Type: データ型を指定(Object, Array, String, Integer など)
  • Description: 各フィールドの意味を明確に記述することで、より正確な応答が得られます
  • Required: 必須フィールドを指定することで、常に必要なデータが含まれることを保証します
  • ResponseMimeType: ApplicationJson を指定することで JSON 形式での応答を強制します

この機能を使うことで、自由形式のテキストではなく、アプリケーションで直接処理できる構造化データを取得できます。

FunctionCallingの利用

Function Calling は、Gemini がユーザーの意図を理解し、適切な関数を呼び出すべきタイミングを判断してくれる機能です。これにより、外部 API やデータベースとの連携が容易になります。

実行例

天気情報を取得する例を見てみましょう。

using GeminiAPIWrapper;
using GeminiAPIWrapper.Configurations;
using GeminiAPIWrapper.Extensions;
using GeminiAPIWrapper.Options;
using SampleApp;
using System.Text.Json;

var options = new GenerativeLanguageOptions() { ApiKey = "{YourAPIKey}", EndpointBase = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash" };
var service = GeminiServiceBuilder.Build(options);

// 会話履歴
List<Content> history = [];

// ユーザーメッセージで関数呼び出しを要求
var request = FunctionCallRequest.BuildRequest(
    userMessage: "東京の現在の天気を教えてください。",
    systemInstruction: "あなたは信頼できる天気予報アシスタントです。"
);

// 会話履歴を追加してコンテキストを維持
history.Add(request.Contents[0]);

// 関数呼び出しを含む応答を取得
var response = await service.GenerateAsync(request);
if (response is null) return;

// 会話履歴にモデルの応答を追加
history.Add(response?.Candidates[0]?.Content);

// 関数呼び出しがあれば処理を行う
var functionCall = response.GetFunctionCall();
if (functionCall is null) return;

// 関数名で処理を分岐
if (functionCall.Name == FunctionCallRequest.functionName)
{
    if (functionCall.Args is null) return;
    // 引数をデシリアライズして利用
    var arg = functionCall.Args.Value.Deserialize<FunctionArgsModel>();
    Console.WriteLine($"Function Call Name: {functionCall.Name}");
    Console.WriteLine($"Location: {arg?.Location ?? string.Empty}");

    // ここで実際の天気情報を取得する処理を実装する(例: 外部API呼び出し)
    var funcResponseModel = new FunctionResponseModel
    {
        Location = arg?.Location ?? string.Empty,
        Temperature = 22,
    };

    //モデルをシリアライズして関数応答として渡す
    var funcResponseJson = JsonSerializer.SerializeToElement<FunctionResponseModel>(funcResponseModel);
    var resRequest = FunctionCallRequest.BuildFuncResponseRequest(
        systemInstruction: "あなたは信頼できる天気予報アシスタントです。",
        functionResponse: funcResponseJson,
        history: history
    );

    // 関数応答を含む新しいリクエストで再度生成を呼び出す
    var resResponse = await service.GenerateAsync(resRequest);
    var resText = resResponse.GetText();
    Console.WriteLine(resText);
    // 出力例: "東京の現在の気温は22度です。"
}

関数定義とリクエスト構築

関数のシグネチャと、それを使ったリクエストの構築方法を定義します。

using GeminiAPIWrapper;
using GeminiAPIWrapper.Configurations;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SampleApp;

internal class FunctionCallRequest
{
    public static readonly string functionName = "get_current_weather";

    private static Tool BuildTool()
    {
        return new Tool
        {
            FunctionDeclarations =
            [
                new FunctionDeclaration
                {
                    Name = functionName,
                    Description = "現在の天気情報を取得します。",
                    Parameters = new Schema
                    {
                        Type = SchemaType.Object,
                        Properties = new Dictionary<string, Schema>
                        {
                            { "location", new Schema { Type = SchemaType.String, Description = "都市名または地域名(例: 'Tokyo', 'New York'" } },
                        },
                        Required = ["location"]
                    }
                }
            ]
        };
    }

    public static GeminiRequest BuildRequest(string userMessage, string systemInstruction)
    {
        var tool = BuildTool();
        return new GeminiRequest
        {
            Contents =
           [
                new Content
                {
                    Role = Role.User,
                    Parts =
                    [
                        new Part
                        {
                            Text = userMessage
                        }
                    ],
                }
           ],
            SystemInstruction = new SystemInstruction
            {
                Parts =
               [
                    new Part
                    {
                        Text = systemInstruction
                    }
               ],
            },
            Tools = [tool],
        };
    }

    public static GeminiRequest BuildFuncResponseRequest(string systemInstruction, JsonElement functionResponse, List<Content> history)
    {
        var tool = BuildTool();
        var content = new Content
        {
            Role = Role.User,
            Parts =
            [
                new Part
                {
                    FunctionResponse = new FunctionResponse
                    {
                        Name = functionName,
                        Response = functionResponse
                    }
                }
            ],
        };
        history.Add(content);

        return new GeminiRequest
        {
            Contents = history,
            SystemInstruction = new SystemInstruction
            {
                Parts =
               [
                    new Part
                    {
                        Text = systemInstruction
                    }
               ],
            },
        };
    }
}

internal record FunctionArgsModel()
{
    [JsonPropertyName("location")]
    public string Location { get; set; } = string.Empty;
}

internal record FunctionResponseModel()
{
    [JsonPropertyName("location")]
    public string Location { get; set; } = string.Empty;
    [JsonPropertyName("temperature")]
    public int Temperature { get; set; }
}

Function Calling のフロー

  1. 関数定義: Tool オブジェクトで利用可能な関数を定義
  2. ユーザーメッセージ送信: 通常のリクエストとして送信
  3. 関数呼び出し検出: Gemini が関数を呼び出すべきと判断した場合、FunctionCall オブジェクトを返す
  4. 関数実行: アプリケーション側で実際の処理を実行(例: 外部APIへのリクエスト)
  5. 結果を返す: 関数の実行結果を FunctionResponse として Gemini に返す
  6. 最終応答: Gemini が関数の結果を使って自然言語の応答を生成

ポイント

  • 会話履歴の管理: history リストで会話のコンテキストを維持することが重要です
  • 引数のデシリアライズ: functionCall.Args を適切な型にデシリアライズして利用します
  • 関数応答のシリアライズ: 実行結果を JsonElement としてシリアライズして返します

この仕組みにより、天気情報、データベースクエリ、外部API呼び出しなど、様々な外部機能を Gemini と統合できます。

まとめ

GeminiAPIWrapper は、Gemini API を .NET アプリケーションで簡単に利用するためのライブラリです。

主な利点:

  • シンプルな API: わずか数行で AI 機能を組み込める
  • 柔軟な設定: ストリーミング、カスタムヘッダー、詳細な生成設定に対応
  • 構造化データ: Response Schema で確実にデータを取得
  • 外部連携: Function Calling で外部システムとシームレスに統合
  • 高パフォーマンス: System.Text.Json のソース生成による高速処理

近日中に NuGet で公開予定ですので、ぜひお試しください。

リポジトリ: GitHub - GeminiAPIWrapper