方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【Gemini Pro API】ChatGPTより80倍安い!? ・Go言語での実装方法

この記事は約13分で読めます。

2023年12月13日にGoogleが次世代AIであるGemini ProAPIをリリースした。

少し時間が過ぎたが、ChatGPTとの価格比較と、実装方法について記載。

価格比較:Gemin Pro API vs ChatGPT

Gemini Pro APIの価格

  • トークン単位ではなく、文字数単位
  • 1000文字あたり、0.00025ドル。outputがinputの2倍の価格
  • フリープランある(1分につき60リクエストまで)
  • まだ従量課金モデルは「Coming soon」になっていて使えない(2024年1月11日時点)。現時点で1分間に60クエリ以上投げたらどうなるんだろう?多分free tierは超えるので返答こないのかな?それともfreeプランのままで返答くるのかな?

https://ai.google.dev/pricing

Open AI APIの価格

https://openai.com/pricing

  • フリープランなし
  • トークンあたりの従量課金:1000トークンで、gpt-4が0.03ドル、gpt-3.5-turbo-1106が0.0010ドル(入力テキスト)
  • 出力テキストが、入力テキストの2倍の価格
  • gpt-3.5は、2024年1月25日時点で、入力が50%値下げ、出力が25%値下げとなった。

文字数とトークン数の関係

トークン

  • 自然言語処理(NLP)で用いられる、文章を分析する際の基本的な単位です。
  • 文章を構成する単語や句読点などを指し、トークン化(tokenization)はこれらの要素に分割するプロセス
  • 英語の場合はスペースや句読点が自然な分割点となるが、日本語の場合は形態素解析が必要。
  • 英語:1単語(≒4文字)=1トークン。句読点もそれだけでトークン扱い
  • 日本語:漢字=2 or 3トークン、ひらがな=1トークン
  • 日本語はトークンベースだと料金が高くなるが、単語・文字数ベースだと料金が低くなる
  • 英語はトークンベースだと料金が低くなるが、文字数ベースだと料金が高くなる

英語の例文とトークン化:

例文: “The quick brown fox jumps over the lazy dog.”
トークン: [“The”, “quick”, “brown”, “fox”, “jumps”, “over”, “the”, “lazy”, “dog”, “.”]
トークン数: 10、文字数: 43 (スペースを含む)

日本語の例文とトークン化:

例文: “きょうはとてもいい天気です。”
トークン: [“きょう”, “は”, “とても”, “いい”, “天気”, “です”, “。”]
トークン数: 7、文字数: 14 (スペースを含まない)

英語ではスペースや句読点で自然にトークンが分かれるが、日本語では単語の区切りがスペースで表されないため、形態素解析器が単語の境界を見つける必要がある。したがって、同じ内容を表す文章でも、言語によってトークン数と文字数に違いが生じる。

トークン数は、Tokenizerで確認することができる。

https://platform.openai.com/tokenizer

価格比較まとめ:Geminiの圧倒的に安い。

今回は、便宜的に、日本語で1文字=2トークンとして計算

1ドル≒150円

OpenAI APIGemini Pro API
価格モデル従量課金 フリープランなしフリーミアムモデル 1分間に60クエリまでフリー
価格(入力テキスト)GPT3.5-turbo-1106:0.01ドル →1.5円/1000トークン GPT4:0.03ドル →4.5円/1000トークン0.00025ドル/1000文字 →0.01875円/1000トークン
価格(出力テキスト)GPT3.5-turbo-1106:0.02ドル →3円/1000トークン GPT4:0.06ドル →9円/1000トークン0.0005ドル/1000文字 →0.0375円/1000トークン

計算間違っているのでは?と疑ってしまったのだが、Gemini Pro APIの方がGPT3.5-turboと比較して80倍近く安い!

OpenAIが課金単位としている「トークン」では割高になりがちな日本語では、「文字」でカウントしてくれるGeminiは割りが良い(逆に英語は割りが悪い)というのもあると思うが、それを差し引いても圧倒的に安い。

ちなみに、Gemini Pro APIの性能はChatGPT3.5と同じくらいと言われている。

Gemini Pro APIキーの設定

Geminiには、「Google AI Studio」というWebインターフェースが用意されており、そこでモデルの詳細をチューニングしたり、実際にモデルを使ったりすることができる。

ai.google.devサイトにアクセスして、「Get API key in Google AI Studio」ボタンをクリック

https://ai.google.dev/

googleが提供している開発者向けサービスを使うには、どのような機能であれ、まずはgoogleクラウドでプロジェクトを作成していることが前提になる。

今回はすでにプロジェクトがあるので、「Create API key in existing project」を選択して、APIキーを発行する。

ターミナルで言われた通りに、作成したAPI keyをクエリに入れて実行すると、すぐにjsonフォーマットで生成した文章が返ってきた。

このシームレスな体験設計良い。

Chat Promptで試してGet code

先ほどはサンプルをターミナルから実行したが、Google AI Studioはプロンプトを試すPlayground環境を用意している(OpenAIと同じ)。

Google AI Studio→Create new→Chat promptを選択。

開いた画面で、天気情報に対して追加の一言メッセージを作成するようなプロンプトを作成してみたら、返信が返ってきた。

内容的にも問題ない。

これで良さそうだと感じたら、画面右上の「Get code」を選択すると、各言語(JavaScript, Python, Kotlin, Swift)で生成されたコードをコピーできる。

これ便利!と思ったのだが、今回使用するのはGo言語なので残念。

Go言語での実装

今回サーバーサイドはGo言語を利用していて、バックエンドでGemini pro APIを利用したいので、go言語のチュートリアルを参照。

https://ai.google.dev/tutorials/go_quickstart?hl=ja

generative-ai-goパッケージをインストール

Go
go get github.com/google/generative-ai-go

generative-ai-goパッケージのドキュメントはこちら

https://pkg.go.dev/github.com/google/generative-ai-go/genai

Go言語でGemini Pro APIを使用

Go
package clocky_be

import (
	"context"
	"fmt"
	"log"

	"github.com/google/generative-ai-go/genai"
	"google.golang.org/api/option"
)

func GenerateText(config *Config) {
	log.Println("GenerateText function called")
	ctx := context.Background()

	GeminiAPIKey := config.GeminiAPIKey
	// Access your API key as an environment variable (see "Set up your API key" above)
	client, err := genai.NewClient(ctx, option.WithAPIKey(GeminiAPIKey))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// For text-only input, use the gemini-pro model
	model := client.GenerativeModel("gemini-pro")
	prompt := genai.Text("Write a story about a magic backpack.")
	resp, err := model.GenerateContent(ctx, prompt)
	if err != nil {
		log.Fatal(err)
	}
	printResponse(resp)
}

GenerativeModel関数に関するドキュメントはこちら

https://pkg.go.dev/github.com/google/generative-ai-go/genai#GenerativeModel

GenerateContent関数のresponseは*genai.GenerateContentResponse 型。

GenerateContent関数のresponseの構造と中身

resp の構造を知りたいので、Go の encoding/json パッケージでjson.MarshalIndent 関数を使用して resp オブジェクトを整形された JSON 文字列に変換し、それを fmt.Println で出力してみる。

Go
func GenerateText(config *Config) {
	log.Println("GenerateText function called")
	ctx := context.Background()

	GeminiAPIKey := config.GeminiAPIKey
	// Access your API key as an environment variable (see "Set up your API key" above)
	client, err := genai.NewClient(ctx, option.WithAPIKey(GeminiAPIKey))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// For text-only input, use the gemini-pro model
	model := client.GenerativeModel("gemini-pro")
	prompt := genai.Text("今日の天気を教えて")
	resp, err := model.GenerateContent(ctx, prompt)
	if err != nil {
		log.Fatal(err)
	}
	respJSON, err := json.MarshalIndent(resp, "", "  ")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(respJSON))
}

結果がこちら

Go
{
  "Candidates": [
    {
      "Index": 0,
      "Content": {
        "Parts": [
          "申し訳ございませんが、私はリアルタイムの情報を提供することができません。私は2023年2月までに得た知識のみを基に回答を生成しています。最新の天気予報については、気象庁のウェブサイトやテレビ、ラジオなどの天気予報番組をご覧いただくことをお勧めします。"
        ],
        "Role": "model"
      },
      "FinishReason": 1,
      "SafetyRatings": [
        {
          "Category": 9,
          "Probability": 1,
          "Blocked": false
        },
        {
          "Category": 8,
          "Probability": 1,
          "Blocked": false
        },
        {
          "Category": 7,
          "Probability": 1,
          "Blocked": false
        },
        {
          "Category": 10,
          "Probability": 1,
          "Blocked": false
        }
      ],
      "CitationMetadata": null,
      "TokenCount": 0
    }
  ],
  "PromptFeedback": {
    "BlockReason": 0,
    "SafetyRatings": [
      {
        "Category": 9,
        "Probability": 1,
        "Blocked": false
      },
      {
        "Category": 8,
        "Probability": 1,
        "Blocked": false
      },
      {
        "Category": 7,
        "Probability": 1,
        "Blocked": false
      },
      {
        "Category": 10,
        "Probability": 1,
        "Blocked": false
      }
    ]
  }
}

resp の中からテキスト情報(CandidatesContentParts の中身)を取り出したいので、以下のように resp オブジェクトの対応するフィールドにアクセスする。

応答 (resp) に含まれる各候補 (Candidates) をループし、それぞれの候補の内容 (Content) が存在するかを確認。Content が存在する場合、その中の Parts 配列に含まれる各テキスト部分をさらにループし、それぞれを出力する。

Go
func printResponse(resp *genai.GenerateContentResponse) {
    for _, candidate := range resp.Candidates {
        // Content が nil でないことを確認
        if candidate.Content != nil {
            // Parts に含まれる各テキスト部分をループで処理
            for _, part := range candidate.Content.Parts {
                fmt.Println(part)
            }
        }
    }
}

main.goファイルで、GenerateText関数を呼び出して、サーバー起動。

Go
package main

func main() {
	config := clocky.NewConfig()
	clocky.GenerateText(config)
}

完成!

無事、「今日の天気は?」というプロンプトに対して、返答がテキストでコンソールに表示された。返答内容は、まぁ、少し残念だが。

コメント

タイトルとURLをコピーしました