nikkie-ftnextの日記

イベントレポートや読書メモを発信

素振りの記:深さ優先探索(DFS) N度目入門

はじめに

みんな、えらい! nikkieです。

今回は苦手意識のある分野に取り組みます。
私、えらいぞ!

目次

なんとなく苦手意識

実は競技プログラミング(AtCoderのABC)を1桁回数やったことがあります。
A・B問題は解けるのですが、C問題から先が難しかったです。
これは競技プログラミングの基本を身につけていないからだろうなと考えていました。
その1つがグラフの探索です。

グラフの探索アルゴリズムはいくつかあると思いますが、1つは深さ優先探索。
AtCoderの解説でDFSという用語が出てきたことが印象に残っています(depth-first search)。

学生時代に学んだ気はするのですが、「再帰」という概念に苦手意識があり、DFSを自分のものにできていない感覚でした。
必要に迫られて今回の素振りです。

『問題解決力を鍛える!アルゴリズムとデータ構造』

積ん読から手に取りました。
13章がグラフ探索です。
「13.3 再帰関数を用いる深さ優先探索」が今の私にはドンピシャでした。

C++の実装例

Pythonで写経:再帰を使ったDFS

以下のグラフを深さ優先探索します。

graph LR
  0-->5
  1-->6
  1-->3
  2-->7
  2-->5
  3-->7
  3-->0
  4-->1
  4-->6
  4-->2
  6-->7
  7-->0

  • 8頂点(番号0〜7)、12辺
  • graphはリストのリスト
    • graph[0]の[5]は、頂点0から頂点5への辺があることを示す
    • graph[1]の[6, 3]は、頂点1から頂点6、頂点3への2つの辺があることを示す
  • 各頂点が訪問済みかを表す配列seen
    • 訪問済みならスキップ(再度処理しない)
  • dfs関数(再帰関数)で頂点iの処理
    • 頂点iを訪問済みにする
    • 頂点iから辿れる頂点について
  • ノードを1回ずつたどるようにfor文でrangeを回している

実行結果

% hatch run python script.py  # Python 3.12.0 で実行されました
main: Visiting node 0
dfs: Visiting node 0
dfs: Visiting node 5
main: Visiting node 1
dfs: Visiting node 1
dfs: Visiting node 6
dfs: Visiting node 7
dfs: Visiting node 3
main: Visiting node 2
dfs: Visiting node 2
main: Visiting node 3
main: Visiting node 4
dfs: Visiting node 4
main: Visiting node 5
main: Visiting node 6
main: Visiting node 7
  • 頂点0から始める(0, 1, 2, ...)
    • 頂点0から頂点5に辿れる
      • 頂点5についてdfs
        • 頂点5は終端
  • 次は頂点1
    • 頂点1から頂点6, 3に辿れる
      • 頂点6から頂点7に辿れる
        • 頂点7から頂点0に辿れるが、0は訪問済み
      • 頂点3から頂点7, 0に辿れる
        • いずれも訪問済み
  • 次は頂点2
  • 以下省略

関連リソース

購読(but 積ん読)しているけんちょんさんのブログの出番でもあるのではと思いました。
以下を眺めたところ学びがいくつもありました。

グラフ探索の問題として、人生で最初に解きたい問題!!

(上のスクリプトは、DFSにまず向き合うために入力の処理を省略したんですよね)

「隣接リスト表現」、「木はグラフの一種ですので、(略) DFS・BFS ができます。」など理論面の知識補強となる記事です

終わりに

苦手意識のあった、再帰を使った深さ優先探索に向き合いました。
今回が何度目になるかは分かりませんが、初見時よりも隣接リスト表現や再帰に驚かずに済み、C++のサンプルコードをPythonで書き換えるまでできました(これが年の功)。
食わず嫌いしていた食材を意を決して食べてみたら、食べられないこと全然なかったという感じです

pipxやuvで、開発者がPython仮想環境を作らない開発 〜Streamlitを例に〜

はじめに

「仮想環境を開発者が作る必要がほとんどなくなっています。」 nikkieです。

この記事は「Pythonの開発で(特定のライブラリ)の入った仮想環境がどこにあるか分からなくなるんだよね」への私なりのアンサーです。
私は仮想環境をツールに委ねることにしたので、上記の悩みから解脱しました。

目次

Streamlitアプリの例

今回は「Pythonの開発で Streamlit の入った仮想環境がどこにあるか分からなくなるんだよね」というお悩みを取り上げます。
動かすStreamlitアプリは、キャッシュのサンプルアプリです。
なお注意書きとして、nikkieはStreamlitアプリの開発はこのキャッシュのサンプルアプリ以降ほとんどやっていません

これまで:仮想環境を作る

上記記事にもありますが、このサンプルアプリを作るにあたってまず人力で1仮想環境を作りました。

作業ディレクトリにて

% python -m venv .venv --upgrade-deps
% source .venv/bin/activate
(.venv) % pip install streamlit
(.venv) % streamlit run app.py

仮想環境でstreamlitコマンドが有効になっています。
このマシンでStreamlitアプリを開発していく場合、この仮想環境を都度有効にしたり、毎回仮想環境を人力で作ったりすることになると思います。

1️⃣ pipx installでstreamlitコマンドが使えるようにする

マシングローバルでstreamlitコマンドを使えるようにします。
pipxはStreamlit用の仮想環境にStreamlitをインストールし2、その仮想環境のstreamlitコマンドにPATHを通します3

% pipx install streamlit
% streamlit --help  # コマンドが使えるようになっています
% streamlit run app.py

streamlitコマンドはバージョン固定ですが、pipx upgrade streamlitで最新化できます。

上記のようなサンプルアプリから発展して他のライブラリが必要になったとき、この方法は少し不便かと思います。
pipx injectでstreamlitコマンドの仮想環境にライブラリを追加できます。
ですが、streamlitコマンドの仮想環境で、あるライブラリのバージョン違いが共存できません。
例えばあるアプリではNumPy 1系、別のアプリではNumPy 2系が必要な場合、1️⃣の方法では共存できないと思われます

2️⃣ uvx streamlitで最新のStreamlitをインストールした仮想環境を用意する

pipx runでもできますが、uvxは速い & ログが分かりやすいので、このセクションではuvxを推します。

% uvx streamlit --help  # 一時的な仮想環境にインストールしてヘルプを見る
% uvx streamlit run app.py
  • uvxは指定したコマンドを提供するパッケージを一時的な仮想環境にインストールします
    • 今回はstreamlitコマンドを提供するStreamlitパッケージをインストールしています
    • コマンドとパッケージの名前が違う場合は、パッケージを--fromで指定します
  • 一時的な仮想環境にStreamlitをインストールして、streamlit run app.pyが実行されました
    • どこまで一時的かはソースを見る必要がありそうに思われます(ドキュメントで見つけられておらず)
    • uvは速い & キャッシュを使い倒しているので、仮に毎回仮想環境を作り直していたとしても私には許容できるような体感です

追加のライブラリは--withで指定します。
こちらの方法は一時的な仮想環境を都度作ることで、1つの仮想環境にライブラリのバージョン違いが共存できない問題を回避していると思われます。

「uvxよいな」と思った方はぜひドキュメントをどうぞ!

終わりに

開発者が仮想環境を作る必要がなくなっている例の紹介でした。
Streamlitのアプリ開発では、環境構築に以下の選択肢もあると考えています

  • pipx install streamlit(マシングローバルでstreamlitコマンドを用意)
  • uvx streamlit run app.py

なおこの考え方はStreamlitと同種のライブラリにも適用できます4。
PyCon mini 東海 2024でReflexのトークを聞いていて、着想しました

P.S. @st.cacheはdeprecated

@st.cache_dataに書き換えました。

「Migrating from st.cache」
https://docs.streamlit.io/develop/concepts/architecture/caching#migrating-from-stcache
選択肢は2つ:st.cache_data or st.cache_resource

使い分けを述べた箇所「Deciding which caching decorator to use」
https://docs.streamlit.io/develop/concepts/architecture/caching#deciding-which-caching-decorator-to-use
今回の関数はintを返すので、st.cache_dataを選択(「Simple computations with basic types」のケース)


  1. Pythonチュートリアルで開発者はパッケージマネージャに仕立て上げられます
  2. 興味がある方はls $(pipx environment --value PIPX_LOCAL_VENVS)をご覧ください
  3. pipxの仕組みを理解するうえでオススメは『ハイパーモダンPython』2章です
  4. Streamlitと並び立つGradioは、gradioコマンドがあるわけではないようなので、inline script metadataと相性がよさそうですね。ref: Quickstart

Vertex AIのGemini APIで出力をJSON形式で構造化するには(google-cloud-aiplatform、google-genai)

はじめに

TAAFアニメファン賞、ミリアニにお願いします! nikkieです。

OpenAIのGPTではおなじみのJSON形式出力をGeminiでどうやるのか調べています。
前回はGoogle AI篇で、今回はVertex AI篇です1。

目次

ドキュメントより

google-cloud-aiplatform

cloud.google.com

JSONスキーマを表す辞書を手で書いて指定します。
同じGeminiのはずなのですが、google-generativeaiのようにTypedDictで2渡せないのが謎です(ライブラリにプルリクチャンスなのかな?)

ドキュメントには、Gemini 1.5 Flash・Gemini 1.5 Proのモデルの記載がありました。

cookbookより

google-genai

1つのライブラリで、Google AIのGemini APIもVertex AIのGemini APIも叩ける新星。
ドキュメントを参考にPydantic.BaseModelを継承したクラスを渡してみましょう。
https://googleapis.github.io/python-genai/#json-response-schema

動作確認スクリプト

Vertex AIが有効なGoogle Cloudのプロジェクトをgcloudコマンドのデフォルトprojectに設定しました。
その状態でgcloud auth application-default loginしたので、vertexai.init()にprojectやlocationの指定無しで済んでいます。
一方、google-genaiのClientは、ADCがあってもprojectやlocationの指定がいるようでした(省略したら 503 Service Unavailable になってハマりました)3

google-cloud-aiplatformでプロンプト・Gemini 1.5

Vertex AIのドキュメントには記載がないですが、試しました。
Google AI同様にJSONのコードブロックで返ってきており、欲しい形(JSONそのもの)ではありません。

※実際はバッククォートのみです(バックスラッシュはエスケープの意図です)

\`\`\`json
[
  {
    "recipe_name": "Chocolate Chip Cookies",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "¾ cup granulated sugar",
      "¾ cup packed brown sugar",
      "1 teaspoon pure vanilla extract",
      "2 large eggs",
      "2 ¼ cups all-purpose flour",
      "1 teaspoon baking soda",
      "1 teaspoon salt",
      "2 cups chocolate chips"
    ]
  },
  # 省略
]
\`\`\`

google-cloud-aiplatformで手書きJSONスキーマ・Gemini 1.5

JSONで得られました!

[
  {
    "recipe_name": "Chocolate Chip Cookies",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "1 cup granulated sugar",
      "1 cup packed brown sugar",
      "2 teaspoons pure vanilla extract",
      "2 large eggs",
      "3 cups all-purpose flour",
      "1 teaspoon baking soda",
      "1 teaspoon salt",
      "2 cups chocolate chips"
    ]
  },
  // 省略
  {
    "recipe_name": "Snickerdoodles",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "1 1/2 cups granulated sugar",
      "2 large eggs",
      "2 3/4 cups all-purpose flour",
      "2 teaspoons cream of tartar",
      "1 teaspoon baking soda",
      "1/4 teaspoon salt",
      "2 tablespoons granulated sugar",
      "2 teaspoons ground cinnamon"
    ]
  }
]

google-genaiでPydanticのBaseModel・Gemini 2.0

JSONで得られました!
google-genaiでlistが未サポートのようだったので、レシピの例ではなくgoogle-genaiのドキュメントの例です。

{
  "capital": "Washington D.C.",
  "continent": "North America",
  "gdp": 25000000000000,
  "name": "United States",
  "official_language": "English",
  "population": 331000000,
  "total_area_sq_mi": 3797000
}

google-genaiでGoogle AIとVertex AIのコードが統合されたのはとてもありがたいです

終わりに

Vertex AIのGemini APIでJSON出力を得るには以下の方法があります。

  • google-cloud-aiplatformで手書きJSONスキーマを渡す(Gemini 1.5)
  • google-genaiでPydanticのBaseModelを継承したモデルを渡す(Gemini 2.0)
    • Google AIと共通!

google-generativeaiはTypedDictでしたが、なんとVertex AIとは互換性がありません!(みんなちがって、みんないい...??)
google-genaiの登場でGoogle AI・Vertex AIを同じコードで叩けるようになったのは福音ですが、まだまだ発展途上のライブラリなので機能拡充が待たれます🙏(プルリクチャンス!)


  1. Gemini APIはGoogle AIとVertex AIの双方から利用できます
  2. 前回記事のこの辺り Google AIのGemini APIで出力をJSON形式で構造化するには(google-generativeai、google-genai) - nikkie-ftnextの日記

Google AIのGemini APIで出力をJSON形式で構造化するには(google-generativeai、google-genai)

はじめに

メダリスト「一番上手になりたい」、泣いた😭 nikkieです。

OpenAIのGPTではおなじみのJSON形式出力1。
「Geminiでどうやるんだろう」と手を動かしました。
今回はGoogle AI篇です(Vertex AIは登場しません)2

目次

ドキュメントより

ライブラリによって方法が異なると理解しました。

google-generativeai

ai.google.dev

「JSONを生成」より、モデルにJSONスキーマを提供する方法は2つ

どちらのアプローチも、Gemini 1.5 Flash と Gemini 1.5 Pro の両方で機能します。

cookbookより

google-genai

https://googleapis.github.io/python-genai/#json-response-schema

Gemini 2.0 Flashに渡しているのは、PydanticのBaseModelを継承したモデルです。
またJSONスキーマの辞書を手で書いてもよさそうでした

動作確認スクリプト

Google AI Studioで払い出したAPIキーを、環境変数GOOGLE_API_KEYに指定しています。

google-generativeaiでプロンプト・Gemini 1.5

これ、ちょっと欲しい形と違いました。
マークダウンのコードブロックで返ってきています。
ほしいのはコードブロックの中身なんですよね。
プロンプト調整の余地がありそうです(正規表現で抜き出せないこともないかな)

※実際はバッククォートのみです(バックスラッシュはエスケープの意図です)

\`\`\`json
[
  {
    "recipe_name": "Chocolate Chip Cookies",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "1 cup granulated sugar",
      "1 cup packed brown sugar",
      "2 teaspoons pure vanilla extract",
      "2 large eggs",
      "3 cups all-purpose flour",
      "1 teaspoon baking soda",
      "1 teaspoon salt",
      "2 cups chocolate chips"
    ]
  },
  # 省略
  {
    "recipe_name": "Snickerdoodles",
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "1 3/4 cups granulated sugar",
      "2 large eggs",
      "2 teaspoons cream of tartar",
      "1 teaspoon vanilla extract",
      "3 cups all-purpose flour",
      "2 teaspoons ground cinnamon",
      "1/4 teaspoon salt",
      "Coarse granulated sugar for rolling"

    ]
  }
]
\`\`\`

google-generativeaiでTypedDict・Gemini 1.5

JSONで得られました!

[
  {
    "ingredients": [
      "1 cup (2 sticks) unsalted butter, softened",
      "1 1/2 cups granulated sugar",
      "1 cup packed brown sugar",
      "2 large eggs",
      "2 teaspoons vanilla extract",
      "3 cups all-purpose flour",
      "1 teaspoon baking soda",
      "1 teaspoon salt",
      "1 cup chocolate chips"
    ],
    "recipe_name": "Chocolate Chip Cookies"
  },
  // 省略
  {
    "ingredients": [
      "1/2 cup (1 stick) unsalted butter, softened",
      "1/2 cup shortening",
      "1 cup granulated sugar",
      "1/2 cup packed brown sugar",
      "1 large egg",
      "1 teaspoon vanilla extract",
      "2 1/4 cups all-purpose flour",
      "1 teaspoon baking soda",
      "1/2 teaspoon salt",
      "1/2 cup oatmeal"
    ],
    "recipe_name": "Oatmeal Cookies"
  }
]

google-genaiでPydanticのBaseModel・Gemini 2.0

JSONで得られました!
ただし複数のレシピの例はPydanticのValidationError3が解決できず、ドキュメントの例を使いました。

{
  "capital": "Washington, D.C.",
  "continent": "North America",
  "gdp": 25460000000000,
  "name": "United States",
  "official_language": "English",
  "population": 331000000,
  "total_area_sq_mi": 3797000
}

終わりに

Google AIのGemini APIでJSON出力を得るには以下の方法があります。

  • google-generativeaiでTypedDictを渡す(Gemini 1.5)
    • プロンプトエンジニアリングでもいけるかもしれません(ドキュメント記載が再現しなかったので、私はTypedDictにしたいと思いました)
  • google-genaiでPydanticのBaseModelを継承したモデルを渡す(Gemini 2.0)

もしかするとgoogle-generativeaiからGemini 2.0や、google-genaiからGemini 1.5も叩けるかもしれませんが、今回の検証はここまでとします(方法が1つは見つかったということで)

Vertex AIについては別で書こうと考えていますが、なんと異なる方法なんですよ...


  1. https://platform.openai.com/docs/guides/structured-outputs/json-mode
  2. Gemini APIはGoogle AIとVertex AIの双方から利用できます
  3. list[Recipe]を持たせたRecipesクラスを導入したのですが、 https://errors.pydantic.dev/2.10/v/extra_forbidden が案内されます。Issueを見たところlistが未サポートで対応中とのこと ref: https://github.com/googleapis/python-genai/issues/60#issuecomment-2577440301

HatchでPythonプロジェクト開発体験記(テスト篇)

はじめに

東ルビ、ありがとうございました!1 nikkieです。

先日emi-reをリリースしました。

この開発でHatchを使っての雑感を綴ります。
今回はテストに関するものになります

目次

HatchでPythonプロジェクト開発

全体感を掴むには、Python Monthly Topicsの記事がオススメです2

Hatchは、Pythonプロジェクトの管理を支援するコマンドラインツールです。

uv同様、プロジェクトだけでなくPython処理系も管理できます3。
またuvとの差分は(執筆時点では)Hatchの方が手厚いと感じます4

Hatchでテスト実行

hatch testを使います。
https://hatch.pypa.io/latest/cli/reference/#hatch-test

hatch test --randomize --all -vv --doctest-modules

出力を見るにpytestが使われていますね

ランダムに実行

--randomize指定により、テストの実行順をランダムにします。
pytest-randomly5を使っているようです

Pythonのバージョンを指定

--allはpyproject.tomlの記載と合わさります。

[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.9", "3.10", "3.11", "3.12", "3.13"]

Pythonバージョンのmatrixに沿って、emi-reでは5つのバージョンでテストが流れます。

Hatchを導入していないプロジェクトでは、GitHub ActionsのmatrixでPythonのバージョンを変えていました6。
HatchだとGitHub Actionsでmatrixを書かなくてよくなるのかもしれません7。

pytestに引数を渡す(verboseやdoctest実行)

doctestを実行しようとして少しハマりました。

pytest --doctest-modulesとして、doctestで書かれたテストもpytest(ランナー)で実行できます。
https://docs.pytest.org/en/stable/how-to/doctest.html
pytestはpyproject.tomlでも設定できるので、はじめは以下のように書きました。

[tool.pytest.ini_options]
addopts = "--doctest-modules"

ところが、hatch testから実行するpytestはpyproject.tomlに書いたini_optionsを無視します

issueから、workaround hatch test --doctest-modulesを知ります。
どうやら、pytestの引数もhatch testで渡せるようです8。
そこで--doctest-modulesの他に-vvも指定しました(落ちたときにdiffが詳しく出る設定)

終わりに

Pythonプロジェクトの開発でHatchを試しました。
結果、テストについて以下に気づきました

  • HatchにPythonバージョンのmatrixを渡せる
    • GitHub Actionsのmatrixは不要になるかも
  • pytestの引数は(pyproject.tomlではなく)hatch testに指定して渡せる

GitHub Actionsがスッキリしたのは嬉しかったですが、Hatchから実行するpytestをpyproject.tomlで設定できないのは意外でした。

なお、hatch testも全て体験したわけではなく、カバレッジなどが宿題です。
引き続き(ハッチポッチ2の座席を得るため9 uvとは別の選択肢を知るために)触っていきます


  1. 東京リベンジャーズ -> 東リベ。すなわち、東京Ruby会議 -> 東ルビ
  2. 他の記事としては、手前味噌ですが
  3. 過去に比較した記事
  4. Hatchが用意するpyproject.tomlはpytestやRuff、mypyを使うように設定されます。この点でPython開発のプラクティスが適用されるツールと私は感じます(対してuvだとツールの選定から必要です) https://ftnext.github.io/2024-slides/pyconjp/pep723-inline-script-metadata-world.html#/19/1
  5. PyCon JP 2019 ビギナーセッションより
  6. 例 https://github.com/ftnext/sphinx-new-tab-link/blob/v0.6.1/.github/workflows/testing.yml#L12-L15
  7. matrixを書いて、hatch testに--allはつけない案も浮かんでいます。toxを使ってる感じかも ref: https://github.com/PyCQA/flake8/blob/7.1.1/.github/workflows/main.yml
  8. 実装を眺めた感じ、hatch testの可変長位置引数が渡っていきそうでした https://github.com/pypa/hatch/blob/hatch-v1.14.0/src/hatch/cli/test/__init__.py

Pythonライブラリ MainContentExtractor で、HTMLからメイン部分を取り出す(マークダウン変換もサポート!)

はじめに

こんなに寒いのにもう8月か〜、nikkieです。

Today(※最近) I learned な小ネタです。

目次

MainContentExtractorでHTMLからメインの部分を取り出す

https://pypi.org/project/MainContentExtractor/

inline script metadataで動かします。

% uv run script.py https://nikkie-ftnext.hatenablog.com/entry/happy-birthday-emily-chang-2024 contents.md

渡したのはエミリーちゃんのお誕生日をお祝いする記事。
マークダウンで取得できています!

### はじめに

[実は毎日「エミリーちゃんかわいい」しか考えてません](https://twitter.com/ftnext/status/1744258523196989579)、nikkieです。

(ç•¥)
  • uv 0.5.17 (Homebrew 2025-01-10)
  • Python 3.11.8
  • MainContentExtractor 0.0.4
  • HTTPX 0.28.1

知ったきっかけ

話題のbrowser-useが依存していて知りました。
https://github.com/browser-use/browser-use/blob/0.1.23/pyproject.toml#L16
MainContentExtractorは0.0.4というバージョンなので、攻めた採用に映ります1。

MainContentExtractorのドキュメントより

日本語のREADMEがありました。
上記スクリプトはREADMEのHowToUseをベースにしています2

trafilaturaは非常に優れたメインコンテンツ抽出ライブラリですが、必要なデータが欠落したり、HTMLを出力できないといった問題があります。
これらの問題に対処するために、本ライブラリがあります。

trafilaturaは名前だけ聞いたことがありました3。

MainContentExtractor.extract()のoutput_format引数は、マークダウン以外も指定できます4。

HTML形式の他にText形式、Markdown形式での出力もサポートしています。

マークダウンへの変換を担うのは、html2text!

https://pypi.org/project/html2text/

似たツール

過去に記事にしたFireCrawlが浮かびます

  • MainContentExtractor:HTMLの取得は提供されない(HTTPXのような別ライブラリ)。マークダウン出力できる
  • FireCrawl:クロール(HTML取得)も、マークダウン出力も

私がまだ触っていないだけで、似たツールは他にもあるように思います

終わりに

Pythonライブラリ MainContentExtractor で、HTMLからメインの部分を取り出せます。

  • HTMLの取得は提供されない(別のライブラリを使う)
  • 出力はマークダウン、HTML、プレーンテキストをサポート

リポジトリは放置気味、PyPIのバージョンよりもリポジトリが最新という状況ですが、browser-useが使っているという実績があります。


  1. リポジトリを見ると、0.0.4の後にプルリクマージされてますがリリースはなさそうで、もしやうち捨てられているのかもしれません
  2. 宿題事項の1つ:response.encoding = "utf-8"と代入して常に問題ないのか(心配してるのはencodingがutf-8でないとき)
  3. 聞いた勉強会
  4. ref: https://github.com/HawkClaws/main_content_extractor/blob/6f23968db3c9223dc25547ac1e0f68dcdeea6ec2/main_content_extractor/main_content_extractor.py#L30

GoogleのGeminiの叩きかた 〜2つのAPI・3つのSDK〜

はじめに

あんた、またSDK増やして! nikkieです。

Google製のLLM、Geminiについて、整理のために知っていることを書き出します

目次

APIは2つのサービスで提供される

Google AIとVertex AIの2つがあります。

cloud.google.com

上記リンクを確認した私の理解はこちら

  • Google AIのGemini
    • 無料で使える
    • プロトタイピング向け(本番用途ではなさそう)
  • Vertex AIのGemini
    • こちらが本番用途
    • Google Cloudの課金(クレジットカード登録)が必要な認識

3つの公式SDK

  • Google AIは、APIキーを払い出して使います
  • Vertex AIは、application default credentials (ADC)で認証します1

google-generativeai(Google AI向け)

https://pypi.org/project/google-generativeai/

The Google AI Python SDK is the easiest way for Python developers to build with the Gemini API.

generativelanguage.googleapis.com を叩くライブラリという理解です

PyPI (README)ではGoogle AI StudioからのAPIキーの払い出しについても案内されています。

% uv run --python 3.12 --with google-generativeai python  # 3.12.5
>>> import google.generativeai as genai
>>> model = genai.GenerativeModel('gemini-2.0-flash-exp')
>>> response = model.generate_content("The opposite of hot is")
>>> print(response.text)
The opposite of hot is **cold**.

積んでるcookbook

google-cloud-aiplatform(Vertex AI向け)

https://pypi.org/project/google-cloud-aiplatform/

セットアップ手順

  • 請求を有効にしたプロジェクト
  • Vertex AI APIを有効にする
  • gcloud auth application-default login(他の「認証の設定」方法もあるようでした(宿題))

叩いているURLは、ロケーションとプロジェクトから決まるという理解です。
参考:Vertex AI のロケーション  |  Google Cloud

% uv run --python 3.12 --with google-cloud-aiplatform python  # 3.12.5
>>> import vertexai
>>> vertexai.init(project="...", location="us-central1")
>>> from vertexai.generative_models import GenerativeModel
>>> model = GenerativeModel("gemini-2.0-flash-exp")
>>> response = model.generate_content("What's a good name for a flower shop that specializes in selling bouquets of dried flowers?")
>>> response.text[:50]
'Okay, here are some name ideas for a dried flower '

Vertex AI向けのexample(積んでる)

双方サポート、新生 google-genai

https://pypi.org/project/google-genai/

ただし、新しすぎます
ref: https://googleapis.github.io/python-genai/

This is an early release (version 0.1.0). API is subject to change. Please do not use this SDK in production.

クライアントの初期化方法で区別します

Google AIを叩くとき(環境変数GOOGLE_API_KEYを設定)

client = genai.Client()  # vertexaiはデフォルトでFalse

Vertex AIを叩くとき

client = genai.Client(
    vertexai=True, project='your-project-id', location='us-central1'
)
% uv run --python 3.12 --with google-genai python  # 3.12.5
>>> from google import genai
>>> googleai_client = genai.Client()
>>> response = googleai_client.models.generate_content(model='gemini-2.0-flash-exp', contents="The opposite of hot is")
>>> print(response.text)
The opposite of hot is **cold**.

>>> vertexai_client = genai.Client(vertexai=True, project="...", location="us-central1")
>>> response = vertexai_client.models.generate_content(model='gemini-2.0-flash-exp', contents="What's a good name for a flower shop that specializes in selling bouquets of dried flowers?")
>>> response.text[:50]
'Okay, here are some name ideas for a dried flower '

Gemini 2.0からはgoogle-genai推しに見えます(積んでるexampleリポジトリ2つの検索結果より)

おまけ:LangChainがまたややこしい

名称が公式SDKと完全に対応しておりません!

Google AI向け、langchain-google-genai

https://pypi.org/project/langchain-google-genai/

(名前の部分文字列のgoogle-genaiではなく、)google-generativeaiに依存しています。
https://github.com/langchain-ai/langchain-google/blob/libs/genai/v2.0.8/libs/genai/pyproject.toml#L16

Vertex AI向け、langchain-google-vertexai

https://pypi.org/project/langchain-google-vertexai/

公式SDKが旧称のため呼応していないですが、分かりやすくはありますね。
https://github.com/langchain-ai/langchain-google/blob/libs/vertexai/v2.0.11/libs/vertexai/pyproject.toml#L16

(LangChainのこの2つは、google-genaiをサポートする予定があるのだろうか?)

終わりに

GoogleのGemini APIを叩くためのGoogle Cloudのサービス、提供されるSDKについて書き出しました。

  • Gemini APIはGoogle AIとVertex AIの2つ
  • それぞれのSDK、google-generativeaiとgoogle-cloud-aiplatform
    • 両方サポート google-genai
  • LangChainでは一致しない名称に注意

Gemini APIが2つあることを把握していないと、「Gemini API使ってます」というユーザ同士でも話が噛み合わないことがありそうです。
Googleさん、OpenAIをなりふり構わず追いかけてるなという印象ですね(ぶっちゃけると整理してくれ〜🙏)。

さらにOpenAI互換のエンドポイントもあります!2
以下記事の例のURLからGoogle AIのみなのかなと思ってます(Vertex AIでは未検証)


  1. ADCということは、google-authを使ってるんだと思ってます
  2. こちらで使いました