🦔

Gemini ProのAPIを使ってみた

2024/05/17に公開

概要

2024/05/17時点での内容です。
生成AIを使用して、SNSでの投稿に対して自動で応答するbotを作ろうと思い、比較的安そうなGemini Proを使用することにしました。
Geminiはアップデートが速く、ライブラリを使用するとついて行けないタイミングがあること、公式が生のAPIの叩き方を提供してくれていることから、APIを生で叩くようにしました。

  • 使用技術
    • Go(1.21)
    • Gemini Pro 1.5 flash

実装

特に難しいことはなく、リクエストボディを組み立ててGoのhttpライブラリからリクエストを送信しています。

token, err := google.DefaultTokenSource(context.Background(), "https://www.googleapis.com/auth/cloud-platform")

response, err := resty.New().
    SetTimeout(1 * time.Minute).
    R().
    SetHeaders(map[string]string{
        "Content-Type":  "application/json",
        "Authorization": fmt.Sprintf("Bearer %v", token),
    }).
    SetBody(map[string]any{
        "contents": v.createRequestContent(*input),
        "generation_config": map[string]float32{
            "maxOutputTokens": 4096,
            "temperature":     0.8,
            "topP":            0.8,
        },
        "safetySettings": []map[string]string{
            {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_ONLY_HIGH"},
            {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_ONLY_HIGH"},
            {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_ONLY_HIGH"},
            {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_ONLY_HIGH"},
        },
    }).
    Post(fmt.Sprintf("https://us-central1-aiplatform.googleapis.com/v1/projects/%v/locations/us-central1/publishers/google/models/gemini-pro:generateContent", os.Getenv("GOOGLE_CLOUD_PROJECT_ID")))

悩んだ点として、APIを叩くためのアクセストークンの取得があります。CLIではgcloud auth print-access-tokenで取得できますが、プログラム上で取得する方法がわかりづらかったです。
実行するアカウント/サービスアカウントにはVertex AI User相当のロールが必要です。

なお、PythonやNode.jsのライブラリではライブラリ上でいい感じにやってくれるので不要です。

レスポンス

下記のレスポンスが返ってきます。
エンドポイントがgenerateContentの場合、candidateは1つで返ってくるようです。

{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "こんにちは! 👋  何かお困りですか? 😊  お気軽にご質問ください。 😄 \n"
          }
        ]
      },
      "finishReason": "STOP",
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.16545822,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.04401865
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.05350215,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.06928941
        },
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.1625129,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.052716404
        },
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.12689333,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.03120282
        }
      ]
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 1,
    "candidatesTokenCount": 20,
    "totalTokenCount": 21
  }
}

エンドポイントがstreamGenerateContentの場合、candidatesは複数返ってきます。
テキストが複数に分割されて生成されているため、使用する際は繋げる必要があります。

[{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "こんにちは"
          }
        ]
      }
    }
  ]
}
,
{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "! 👋  何かお困りですか? 😊 \n"
          }
        ]
      },
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.17881244,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.06278921
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.0715912,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.06928941
        },
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.20386598,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.06536579
        },
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE",
          "probabilityScore": 0.11920293,
          "severity": "HARM_SEVERITY_NEGLIGIBLE",
          "severityScore": 0.034293946
        }
      ]
    }
  ]
}
,
{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": ""
          }
        ]
      },
      "finishReason": "STOP"
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 1,
    "candidatesTokenCount": 13,
    "totalTokenCount": 14
  }
}
]

safetyRatingsは、生成されたテキストがセーフティに対してどれくらいの数値になっているかが格納されています。
finishReasonは終了の理由が設定されています。ここの値を見て分岐すると良さそうです。

Discussion