10 月 27 日の Gemini API の更新履歴を読んでいて、gemini-api というコマンドラインのクライアントがベータで加わったことを知りました。手が止まったのは、その一行を読んだ直後です。
名前が似ていて、どちらも Google のモデルを呼びます。agy -p で足りる用事を別のコマンドに分けるべきなのか、逆に、読むだけの用事までリポジトリを触れる agy に任せてよいのか。個人開発で一人きりのレビューをしている身には、使い始める前に線を引いておきたい話でした。
最初にお伝えしておきます。私が手元で確かめたのは、振り分けスクリプトの分岐です。gemini-api 本体はベータで、実行ファイルを取り込むことはせず、引数を記録して終了コードを返す偽のコマンドで検証しました。gemini-api の仕様は、公式ドキュメントに書かれていることだけを根拠にしています。
公式ドキュメントで確かめた gemini-api の輪郭
Gemini API CLI のガイドと、10 月 27 日の更新履歴から、振り分けに関わる部分だけを抜き出します。
- ステートレスで依存ゼロのクライアントとされています。Interactions API を通じて、テキストや画像の生成、ファイルの分析、Gemini のマネージドエージェントの実行を行います
- オープンソースのベータで、マイナー版のあいだに破壊的変更が入りうると明記されています。自動化の環境では版を固定します
analyze -i <ファイル>は PDF・画像・音声・動画・CSV・テキストを読みます。インラインの入力は生データで 14MB までで、それを超える場合はfiles uploadを使います。アップロードしたファイルは 48 時間で削除されます- 終了コードは 0(成功)、1(実行時エラー・API エラー)、2(引数や検証の誤り)、3(認証・認可の失敗)です
generateは既定で標準出力にストリームします。-o jsonや--jqと組み合わせるときは--stream=falseが必要です
一方の agy -p は、プロンプトを 1 つ実行して終了する headless モードです。承認が必要な操作は soft-deny になり、終了コードは 0 のまま stderr に通知が出ます。詳しくは以前の記事にまとめました。
線引きは「書き換えるか、読むだけか」
最初は、依頼文の中身を見て自動で振り分けるやり方を考えました。「直して」「修正」といった語を拾う方法です。けれども、語の拾い方で判定を決めると、「この表を直す必要があるか教えて」のような読むだけの依頼が書き換え側に流れてしまいます。判断を機械にまかせるより、呼ぶ側が一語で宣言するほうが、あとで事故の理由を追いやすいと私は考えました。
そこで玄関には edit、analyze、ask の三つだけを置きました。
| 種類 | 行き先 | 向いている依頼 |
|---|---|---|
edit | agy -p | リポジトリのファイルを読み書きさせたい依頼 |
analyze | gemini-api analyze | 1 つのファイルを読ませて答えだけ欲しい依頼 |
ask | gemini-api generate | 状態を持たない単発の質問 |
54 行のスクリプト
#!/usr/bin/env bash
# route.sh — 依頼の種類を見て agy と gemini-api を振り分ける小さな玄関
set -uo pipefail
LIMIT="${ROUTE_TIMEOUT:-5m}"
INLINE_MAX=$((14 * 1024 * 1024)) # gemini-api の -i は生データ 14MB まで
die() { echo "route: $1" >&2; exit "${2:-64}"; }
need() { command -v "$1" >/dev/null 2>&1 || die "$1 が見つかりません" 127; }
kind="${1:-}"; [ -n "$kind" ] || die "使い方: route.sh edit|analyze|ask ..."
shift
case "$kind" in
edit)
# リポジトリを読み書きさせたい依頼は agy に渡す
need agy
[ "$#" -gt 0 ] || die "依頼文がありません"
timeout "$LIMIT" agy -p "$*"
rc=$?
;;
analyze)
# 1 つのファイルを読ませて答えだけ欲しい依頼は gemini-api に渡す
need gemini-api
file="${1:-}"; shift || true
[ -f "$file" ] || die "ファイルがありません: $file"
[ "$#" -gt 0 ] || die "質問文がありません"
size=$(wc -c < "$file")
if [ "$size" -gt "$INLINE_MAX" ]; then
uri=$(timeout "$LIMIT" gemini-api files upload "$file" --wait) || die "アップロードに失敗しました" 1
input="$uri"
else
input="$file"
fi
timeout "$LIMIT" gemini-api analyze -i "$input" --stream=false "$*"
rc=$?
;;
ask)
# 状態を持たない単発の質問は gemini-api の generate
need gemini-api
[ "$#" -gt 0 ] || die "質問文がありません"
timeout "$LIMIT" gemini-api generate --stream=false "$*"
rc=$?
;;
*) die "未知の種類です: $kind" ;;
esac
case "$rc" in
0) ;;
124) echo "route: ${LIMIT} で打ち切りました" >&2 ;;
2) echo "route: 引数の組み立てが誤っています(終了コード 2)" >&2 ;;
3) echo "route: 認証に失敗しました。GEMINI_API_KEY か whoami を確認してください" >&2 ;;
esac
exit "$rc"ポイントは三つあります。
1 つめは、14MB の線です。analyze はファイルの大きさを測り、上限を超えるときだけ files upload --wait で受け取った files/<id> を -i に渡します。小さなファイルまでアップロードすると、48 時間で消える一時ファイルが増えるだけだからです。
2 つめは、timeout で全体を囲んでいることです。打ち切られた場合の終了コードは、どちらの CLI のものでもなく timeout の 124 になります。公式の 0〜3 と混ざらないので、後段で見分けられます。
3 つめは、終了コードを握りつぶさないことです。2 と 3 だけは理由を一行添え、そのまま呼び出し元へ返します。
手元で確かめた分岐
偽の agy と偽の gemini-api を PATH の先頭に置き、引数を記録するだけにして、次の結果を確かめました。
| 呼び方 | 結果 |
|---|---|
edit "…" | agy -p … が 1 回呼ばれ、終了コード 0 |
小さなファイルで analyze | analyze -i a.txt --stream=false … のみ |
約 15MB のファイルで analyze | files upload big.bin --wait のあと -i files/… |
| 偽の CLI が 3 を返す | 認証の案内が出て、終了コード 3 |
| 偽の CLI が 2 を返す | 引数の誤りの案内が出て、終了コード 2 |
ROUTE_TIMEOUT=1s で 5 秒かかる偽の agy | 打ち切りの案内が出て、終了コード 124 |
| 未知の種類/コマンドが無い | それぞれ 64/127 |
注意点と、まだ確かめていないこと
- 実機の
gemini-apiは動かしていません。ベータなので、フラグ名が変わる可能性があります。使う前にgemini-api --usageで確かめてください - 公式の例には、インストールを
curl ... | bashで行う方法があります。内容を読んでから実行するか、版を固定した配布物を使うほうが安心です agyの終了コード 0 は、soft-deny が起きた場合も含みます。editの結果は、終了コードだけでなく差分を見て判断してください- API キーは
YOUR_API_KEYのようなダミーを置き、環境変数GEMINI_API_KEYで渡します。スクリプトに書き込みません
次のアクション
書き換える依頼は agy へ、読むだけの依頼は gemini-api へ、迷ったら呼ぶ側が一語で宣言します。 この線引きは、ベータの CLI が増えるたびに見直しながら、守っていくつもりです。
まずは手元のシェル履歴から、最近の agy -p を五つ拾い、書き換えを頼んだものと読むだけだったものに分けてみていただければ幸いです。この確認から始めるのが、いちばん手戻りが少ないと考えております。