「なんかよく分からないけど、すごい」で終わらせないために
こんにちは、DeNA セキュリティ技術グループの 黒岩 亮 ( @kakira9618 ) です。
AIエージェント、とくに Gemini CLI のようなコーディングを支援してくれるツールは非常に強力で、私たちの開発体験を大きく変えようとしています。しかし、その一方で、こんな風に感じたことはありませんか?
- 「このファイルの情報、勝手にAIに送られたりしない? 大丈夫かな?」 と、情報管理・セキュリティ面で漠然とした不安を感じる。
- 「このプロンプトだとなんか上手くいくけど、こっちだとダメ。理由が分からない…」 と、挙動の再現性がなく、試行錯誤に疲れてしまう。
- 「インターネットで見つけた秘伝のタレプロンプト、本当に効果あるの?」 と、ベストプラクティスと言われるものの効果を実感できず、疑問に思う。
AIエージェントは非常に便利ですが、その動作がブラックボックスであるがゆえに、私たちは「なんとなく」で使ってしまいがちです。しかし、実務で本当に「使いこなす」ためには、その挙動を正確に理解し、制御下に置くことが不可欠です。
本記事では、そのブラックボックスを解き明かすための強力な武器として、脆弱性診断にも使われるローカルプロキシツールである PacketProxy を使った手法を紹介します。
Gemini CLI がAIとどのような対話をしているのか(コンテキストエンジニアリング)を通信レベルで明らかにすることで、その挙動を理解します。
この記事を読み終える頃には、読者の皆さんはAIエージェントに対する漠然とした不安や疑問を自ら解消し、「なんとなく」から脱却して、AIエージェントを真に信頼できる"相棒"として使いこなすための第一歩を踏み出しているはずです。
本記事でやること・やらないこと
やること
- Gemini CLI を例に、AIエージェントが内部でどのような情報をAIに送信しているかを観測・可視化する手法の紹介
- 観測結果からAIエージェントの基本的な動作原理を理解し、実務上の課題(情報漏洩の懸念、挙動の再現性など)を解決に導く考え方の提示
やらないこと
- Gemini CLI や PacketProxy の網羅的な機能解説
- リバースエンジニアリングそのものを目的としたソースコードの解析
対象読者
- AIエージェント(とくに Gemini CLI)を使っているが、その挙動に疑問や不安を感じているエンジニア
- AIエージェントの内部実装やコンテキストエンジニアリングに興味がある方
- ツールの挙動を自分で確かめないと気が済まない、探求心旺盛な方
AIエージェント/Gemini CLI とは
AIエージェントとは、AIを用いて目的達成のために必要な行動を判断し、自律的に動作するプログラムです。
近年は、プログラミング目的のAIエージェントの開発が盛んであり、2025年もさまざまなタイプのAIエージェントが開発されています。
Gemini CLI は AIエージェントの一種であり、以下の特徴があります。
- Google が開発
- AIモデルとして gemini を利用
- CLI タイプの AI エージェントである
- オープンソース(Apache 2.0)である
- 2025/7 現在、無料で使うことができる
ソースコードは GitHub で公開されています。

Gemini CLI の起動画面のスクリーンショット。公式GitHubより引用。
Gemini CLI を使うと、たとえば「◯◯の機能を実装して」や「(エラーのログを貼り付けて)バグを修正して」と入力するだけで、必要な工程を考え、CLI上で実際に実行してくれます。
PacketProxy とは
PacketProxy は、弊社セキュリティ技術グループが開発・運用する、脆弱性診断用のローカルで動くプロキシツールです。

PacketProxy が動作している画面。GitHubより引用
クライアントからの通信は、ローカルプロキシツールを経由させることで、その通信の中身を見たり、改ざんしたりできるようになります。今回は、通信の中身を見るために、PacketProxyを利用します。

ローカルプロキシを介さない場合(通常のGemini CLIの使い方)

ローカルプロキシを介す場合(本記事でのGemini CLIの使い方)
PacketProxy は基本的な通信プロトコル(HTTP, Web Socket, Protocol Buffer等)だけではなく、最新のプロトコル(HTTP3等)やUDPにも対応し、さらにエンコードモジュールの開発によって独自のプロトコルにも対応できる点が特徴です。
Apache License Version 2.0 の OSS であり、無料で使うことができます。
また、業務の脆弱性診断(スマホアプリの診断等)での利用実績が多くあります(詳細は 内製ツールを使ったチート診断・脆弱性診断 を参照してください)。
今回は、PacketProxy を使い、Gemini CLI の通信を観察します。
Gemini CLI の通信を PacketProxy で取得する手順
早速、PacketProxy を使ってみましょう。
準備
JDK 17以上をシステムにインストールしてください。また、Gemini CLI もインストールし、認証等設定し、通常通り使える状態にしてください。
$ java -version
openjdk version "17.0.15" 2025-04-15 LTS
OpenJDK Runtime Environment Corretto-17.0.15.6.1 (build 17.0.15+6-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.15.6.1 (build 17.0.15+6-LTS, mixed mode, sharing)
$ gemini --version
0.1.11
以下の説明は、Gemini CLI の利用方式を Vertex AI 経由にした場合について記述しています。 他の認証方式を利用した場合、通信先のサーバーや通信内容が異なります。あらかじめご承知ください。
手順1: PacketProxy をダウンロード・ビルドし起動する
PacketProxy をダウンロード・ビルド・起動します。
$ git clone https://github.com/DeNA/PacketProxy.git
$ cd PacketProxy
$ ./gradlew run
初回は5分ほどビルドに時間がかかるかもしれません。次回以降はすぐに起動します(再度コンパイルの必要がない場合)。
GUI が立ち上がります。

PacketProxy初回起動画面
手順2: 設定を行う
早速通信を取得…と行きたいところですが、少々設定が必要です。
手順2-1: プロキシ設定
Options
-> Listen Ports
-> Add
を押し、以下の内容で[保存]ボタンを押します。
- Listen Port: 8080
- Type: HTTP_PROXY
- 転送先サーバ: Upstream Proxyを利用せずに直接サーバー接続
- 署名に利用するCA証明書: PacketProxy per-user CA

PacketProxyのListen Ports設定画面

PacketProxyのListen Ports設定値
この設定によって、localhost の 8080番ポートに HTTP_PROXY
というタイプのプロキシが立ち上がります。
HTTP_PROXY
は プロキシ用の HTTP / HTTPS リクエストを受け取り、本来の相手との間の通信を中継するプロキシです。Gemini CLI は、プロキシ設定に対応しているため、今回は HTTP_PROXY
を利用します。
手順2-2: SSL PassThrough の設定 (Vertex AI利用の場合のみ)
Options
-> SSL PassThrough
で .*\.googleapis\.com
の Enabled のチェックを外します。

PacketProxy SSL PassThroughの画面
SSL PassThrough では通信を解析せずそのまま素通しするサーバー名を設定します。今回は .*\.googleapis\.com
への通信を解析するため、チェックを外します。
手順2-3: 証明書のダウンロード
Options
の下部(要スクロール)に PacketProxy CA Certificates & Private Keys
という項目があります。

PacketProxy CA Certificates & Private Keysの画面
手順2-1で指定した CA 証明書(PacketProxy per-user CA
)を選択し、エクスポートを押します。

PacketProxy CA Certificates & Private Keysのエクスポート画面
証明書(PEM形式)
を選択し、[次へ]を押します。証明書を保存する場所を選び、.pem
の拡張子で保存します。ここでは /Users/akira.kuroiwa/packet-proxy-ca.pem
に保存したとします。
手順3: Gemini CLI を起動
Gemini CLI を起動したいディレクトリに移動し、以下のコマンドで Gemini CLI を起動します。
$ HTTPS_PROXY=http://localhost:8080 NODE_EXTRA_CA_CERTS=/Users/akira.kuroiwa/packetproxy-ca.pem gemini
2つの変数に値を設定し、gemini を起動しています。
HTTPS_PROXY
変数にさきほど建てたHTTP_PROXY
の待ち受けURLを指定しています。これで、Gemini CLIから HTTPS 通信するときに、プロキシを経由することになります。NODE_EXTRA_CA_CERTS
変数に追加で信頼する証明書を指定します。ここに、さきほど保存した証明書のフルパスを指定します(例:/Users/akira.kuroiwa/packet-proxy-ca.pem
)。
PacketProxyでいったん通信をデコード・表示・改ざん・再エンコードするため、通信は PacketProxy によって再署名されます。署名を Gemini CLI に信頼させる必要があります。
手順4: 通信内容を確認する
以上で設定は完了です。Gemini CLI で何かプロンプトを入力してみましょう!
今回は、Gemini CLIを、PacketProxy のリポジトリ直下で起動してみました。
Gemini CLI で試しに 自己紹介してください
と入力してみます。

Gemini CLIに自己紹介してくださいと入力する
PacketProxy を通した通信が始まります。PacketProxy の history
タブをチェックしてみましょう。

自己紹介プロンプトのパケットキャプチャ例(historyタブ; クレデンシャル等はマスク)
上の欄が、通信の一覧(デフォルトではリクエストの時刻順)と概要で、項目を選択すると、その通信の内容が画面下部に表示されます。
画面上部の一覧で、Client Request の URL の記載があるものは リクエスト
、その下の URL の記載がないものは、上の項目に対する レスポンス
となっています。
画面下部では、とりあえず Decoded
タブを選択してください。その下のタブ(Raw
/ Binary
/ json
) はデータの表示方法を切り替えることができます。Gemini CLI + Vertex AI の場合の通信方式は、body に JSON を乗せた HTTPS通信なので、json タブがわかりやすいと思います。

PacketProxy の json 表示
1回のセッション(1個のプロンプト入力に対するリクエスト・レスポンス群)が終了したら、最後(一番下)のデータに「色」をつけると見やすいです。項目を右クリックして、add color (green)
などを選択すると上記スクリーンショットのように色をつけることができます。
通信の大まかな読み方は以上です。色々なプロンプトを試し、通信内容を観察すると発見があります。
実験:通信を覗いて、AIエージェントの「なぜ?」を解明する
ここからは、導入で提示した課題を解決するために、実際にGemini CLI の通信を覗いていきましょう。具体的なプロンプトと、その際の通信内容を観察することで、AIエージェントの挙動の裏側にある「なぜ?」を解明します。
実験1:「情報はどこまで送られている?」を知る
最初の課題は、「意図しない情報がAIに送信されていないか不安」というものでした。これを検証するため、まずはもっともシンプルな「自己紹介してください」というプロンプトを例に、Gemini CLI がどのような情報をAIに渡しているのかを見ていきましょう。
まずは、基本ケースから押さえていきましょう。
実験1では、PacketProxy のリポジトリで 自己紹介してください
と入力した場合において、具体的にどのような通信が行われるのか、詳しく見ていきます。
初めにざっと通信の全体像を見て、それから内容について詳しく見ていくことにします。
実験1で確認する内容は以下の通りです
- ✅️ 1回のプロンプトで発生する全体の通信の流れ
- ✅️ 実際に送受信しているデータの内容(どのようなコンテキストエンジニアリングが行われているか)
- ✅️ 一連のリクエスト・レスポンスのやりとりの終了条件
全体の通信フローを確認する
まずは、さきほどのように、 自己紹介してください
と Gemini CLI に入力した場合の通信結果を見てみましょう。
私の環境では、通信が落ち着くまでに、以下の4個のリクエスト/レスポンスが観測されました。(一部クレデンシャルの内容は ******
でマスクされています)
POST https://oauth2.googleapis.com/token
POST https://{region}-aiplatform.googleapis.com/v1beta1/projects/{project-name}/locations/{region}/publishers/google/models/gemini-2.5-pro:countTokens
(長いので、POST /gemini-2.5-pro:countTokens
と表記します。以降同様です。)POST /gemini-2.5-pro:streamGenerateContent
POST /gemini-2.5-pro:generateContent
それぞれ通信を見ていきましょう。
POST https://oauth2.googleapis.com/token
リクエスト(タップで展開)
refresh_token=******&client_id=******.apps.googleusercontent.com&client_secret=******&grant_type=refresh_token
レスポンス(タップで展開)
{
"access_token": "******",
"expires_in": 3599,
"scope": "openid https://www.googleapis.com/auth/sqlservice.login https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email",
"token_type": "Bearer",
"id_token": "******"
}
API 通信を始める前に、必要なクレデンシャルを取得しています。 以前にログイン済み場合、有効期限の長い refresh_token を使い、API通信に必要な access_token / id_token を取得していると考えられます。 Gemini CLI 起動後はじめてプロンプトを入力すると、最初にこのリクエストが送信されます。 これによって、以降の Google 関連の API を実行する準備が整います。
POST /gemini-2.5-pro:countTokens
リクエスト(タップで展開)
{"contents": [
{
"role": "user",
"parts": [{"text": "This is the Gemini CLI. We are setting up the context for our chat.\n Today's date is 2025年7月11日金曜日.\n My operating system is: darwin\n I'm currently working in the directory: /Users/akira.kuroiwa/PacketProxyOSS\n Showing up to 200 items (files + folders). Folders or files indicated with ... contain more items not shown, were ignored, or the display limit (200 items) was reached.\n\n/Users/akira.kuroiwa/PacketProxyOSS/\n├───.gitignore\n├───build.gradle\n├───CONTRIBUTING.ja.md\n├───CONTRIBUTING.md\n├───eclipse-format-settings.xml\n├───gradle.properties\n├───gradlew\n├───gradlew.bat\n├───LICENSE\n├───README.ja.md\n├───README.md\n├───settings.gradle\n├───.git/...\n├───.github/\n│ ├───pull_request_template.md\n│ └───workflows/\n│ └───main.yml\n├───.gradle/...\n├───assets/\n│ ├───images/\n│ │ ├───packetproxy.png\n│ │ └───screenshot.gif\n│ └───resign/\n│ └───entitlements.xml\n├───build/\n│ ├───classes/...\n│ ├───generated/...\n│ ├───reports/...\n│ ├───resources/...\n│ ├───test-results/...\n│ └───tmp/...\n├───gradle/\n│ └───wrapper/\n│ ├───gradle-wrapper.jar\n│ └───gradle-wrapper.properties\n├───installer/\n│ └───izpack/\n│ ├───installer.sh\n│ ├───installer.xml\n│ ├───packetproxy\n│ └───shortcutSpec.xml\n├───libs/\n│ └───agent15.jar\n├───LICENSES/\n│ ├───Agent15-LICENSE\n│ ├───dnsjava-LICENSE\n│ ├───jfontchooser-LICENSE\n│ ├───PacketProxy-LICENSE\n│ └───SSLCapabilitieSSLExplorer-LICENSE\n├───scripts/\n│ └───update-properties\n└───src/\n ├───jmh/\n │ └───java/\n │ └───packetproxy/\n │ └───common/\n │ └───StringSearchBenchmark.java\n ├───main/\n │ ├───java/\n │ │ └───core/\n │ │ ├───org/\n │ │ │ └───xbill/\n │ │ │ └───DNS/\n │ │ └───packetproxy/\n │ │ ├───CertCacheManager.java\n │ │ ├───Duplex.java\n │ │ ├───DuplexAsync.java\n │ │ ├───DuplexFactory.java\n │ │ ├───DuplexManager.java\n │ │ ├───DuplexSync.java\n │ │ ├───EncoderManager.java\n │ │ ├───Listen.java\n │ │ ├───ListenPortManager.java\n │ │ ├───OpenVPN.java\n │ │ ├───PacketProxy.java\n │ │ ├───PPContextMenuManager.java\n │ │ ├───PrivateDNS.java\n │ │ ├───PrivateDNSClient.java\n │ │ ├───Proxy.java\n │ │ ├───ProxyFactory.java\n │ │ ├───ProxyForward.java\n │ │ ├───ProxyHttp.java\n │ │ ├───ProxyHttpTransparent.java\n │ │ ├───ProxyQuicForward.java\n │ │ ├───ProxyQuicTransparent.java\n │ │ ├───ProxySSLForward.java\n │ │ ├───ProxySSLTransparent.java\n │ │ ├───ProxyUDPForward.java\n │ │ ├───ProxyXmppSSLForward.java\n │ │ ├───Simplex.java\n │ │ ├───VulCheckerManager.java\n │ │ ├───common/\n │ │ │ ├───AmazonLexV2.java\n │ │ │ ├───ARC4.java\n │ │ │ ├───Binary.java\n │ │ │ ├───BinaryBuffer.java\n │ │ │ ├───BoyerMoore.java\n │ │ │ ├───CamelCase.java\n │ │ │ ├───ClientKeyManager.java\n │ │ │ ├───ConfigHttpServer.java\n │ │ │ ├───ConfigIO.java\n │ │ │ ├───Connection.java\n │ │ │ ├───CryptUtils.java\n │ │ │ ├───Deflate.java\n │ │ │ ├───Endpoint.java\n │ │ │ ├───EndpointFactory.java\n │ │ │ ├───FilterIO.java\n │ │ │ ├───FilterTextParser.java\n │ │ │ ├───FontManager.java\n │ │ │ ├───GRPCMessage.java\n │ │ │ ├───Hex.java\n │ │ │ ├───I18nString.java\n │ │ │ ├───JWT.java\n │ │ │ ├───JWTBase64.java\n │ │ │ ├───Logger.java\n │ │ │ ├───MessagePack.java\n │ │ │ ├───Parameter.java\n │ │ │ ├───PipeEndpoint.java\n │ │ │ ├───Protobuf3.java\n │ │ │ ├───Range.java\n │ │ │ ├───RawEndpoint.java\n │ │ │ ├───SocketEndpoint.java\n │ │ │ ├───SSLCapabilities.java\n │ │ │ ├───SSLExplorer.java\n │ │ │ ├───SSLSocketEndpoint.java\n │ │ │ ├───StringUtils.java\n │ │ │ ├───TokenHttpServer.java\n │ │ │ ├───UDPConn.java\n │ │ │ ├───UDPConnManager.java\n │ │ │ ├───UDPServerSocket.java\n │ │ │ ├───UDPSocketEndpoint.java\n │ │ │ ├───UniqueID.java\n │ │ │ ├───Utils.java\n │ │ │ └───WrapEndpoint.java\n │ │ ├───controller/\n │ │ │ ├───InterceptController.java\n │ │ │ ├───MainWindowController.java\n │ │ │ ├───PacketsController.java\n │ │ │ └───ResendController.java\n │ │ ├───encode/\n │ │ │ ├───EncodeAmazonLexV2.java\n │ │ │ └───EncodeCBOR.java\n │ │ │ └───...\n │ │ ├───extensions/\n │ │ ├───gui/\n │ │ ├───http/\n │ │ ├───http1/\n │ │ ├───http2/\n │ │ ├───http3/\n │ │ ├───model/\n │ │ ├───ppcontextmenu/\n │ │ ├───quic/\n │ │ ├───util/\n │ │ ├───vulchecker/\n │ │ └───websocket/\n │ └───resources/\n │ ├───log4j.properties\n │ ├───strings_ja.properties\n │ ├───certificates/\n │ │ └───user.ks\n │ ├───gui/\n │ │ ├───arrow.png\n │ │ ├───auto_scroll_disabled.png\n │ │ ├───auto_scroll_enabled.png\n │ │ ├───close_mouseovered.png\n │ │ ├───close.png\n │ │ ├───config.png\n │ │ ├───icon.ico\n │ │ ├───icon.png\n │ │ ├───installer_icon.png\n │ │ ├───installer_leftside_image.png\n │ │ ├───plus.png\n │ │ └───splash.png\n │ └───org/\n │ └───xbill/\n │ └───DNS/\n └───test/\n └───java/\n ├───org/\n │ └───xbill/\n │ └───DNS/\n └───packetproxy/\n ├───PrivateDNSClientTest.java\n ├───common/\n │ ├───AmazonLexV2Test.java\n │ ├───BoyerMooreTest.java\n │ ├───CharSetUtilityTest.java\n │ ├───GRPCTest.java\n │ ├───Protobuf3Test.java\n │ └───StringUtilsTest.java\n ├───http2/\n │ ├───FrameManagerTest.java\n │ └───frames/\n ├───http3/\n │ ├───QpackTest.java\n │ ├───helper/\n │ ├───service/\n │ └───value/\n └───quic/\n ├───service/\n └───value/"}]
},
{
"role": "model",
"parts": [{"text": "Got it. Thanks for the context!"}]
}
]}
レスポンス(タップで展開)
{
"promptTokensDetails": [{
"modality": "TEXT",
"tokenCount": 2278
}],
"totalBillableCharacters": 4179,
"totalTokens": 2278
}
何やらリクエストの contents.parts
でさまざまな情報を渡しているようです(渡している情報の詳細については、後述します)。
これが本記事で知りたかった「AIに渡している情報」そのものになりますが、このリクエストでは、レスポンスを見るとわかるように、そのトークン数をカウントしています。パスの :countTokensのドキュメント を検索すると、確かにそのような動作をするAPIであることが分かります。
この段階では、まだAIによる生成は行われていないようです。この後に実際に生成実行するAPIを呼んでいることを考えると、ここでの目的は「消費する予定のトークン数を計算している」と考えると良さそうです。
POST /gemini-2.5-pro:streamGenerateContent
リクエスト(タップで展開)
{
"contents": [
{
"role": "user",
"parts": [{"text": "This is the Gemini CLI. We are setting up the context for our chat.\n Today's date is 2025年7月11日金曜日.\n My operating system is: darwin\n I'm currently working in the directory: /Users/akira.kuroiwa/PacketProxyOSS\n Showing up to 200 items (files + folders). Folders or files indicated with ... contain more items not shown, were ignored, or the display limit (200 items) was reached.\n\n/Users/akira.kuroiwa/PacketProxyOSS/\n├───.gitignore\n├───build.gradle\n├───CONTRIBUTING.ja.md\n├───CONTRIBUTING.md\n├───eclipse-format-settings.xml\n├───gradle.properties\n├───gradlew\n├───gradlew.bat\n├───LICENSE\n├───README.ja.md\n├───README.md\n├───settings.gradle\n├───.git/...\n├───.github/\n│ ├───pull_request_template.md\n│ └───workflows/\n│ └───main.yml\n├───.gradle/...\n├───assets/\n│ ├───images/\n│ │ ├───packetproxy.png\n│ │ └───screenshot.gif\n│ └───resign/\n│ └───entitlements.xml\n├───build/\n│ ├───classes/...\n│ ├───generated/...\n│ ├───reports/...\n│ ├───resources/...\n│ ├───test-results/...\n│ └───tmp/...\n├───gradle/\n│ └───wrapper/\n│ ├───gradle-wrapper.jar\n│ └───gradle-wrapper.properties\n├───installer/\n│ └───izpack/\n│ ├───installer.sh\n│ ├───installer.xml\n│ ├───packetproxy\n│ └───shortcutSpec.xml\n├───libs/\n│ └───agent15.jar\n├───LICENSES/\n│ ├───Agent15-LICENSE\n│ ├───dnsjava-LICENSE\n│ ├───jfontchooser-LICENSE\n│ ├───PacketProxy-LICENSE\n│ └───SSLCapabilitieSSLExplorer-LICENSE\n├───scripts/\n│ └───update-properties\n└───src/\n ├───jmh/\n │ └───java/\n │ └───packetproxy/\n │ └───common/\n │ └───StringSearchBenchmark.java\n ├───main/\n │ ├───java/\n │ │ └───core/\n │ │ ├───org/\n │ │ │ └───xbill/\n │ │ │ └───DNS/\n │ │ └───packetproxy/\n │ │ ├───CertCacheManager.java\n │ │ ├───Duplex.java\n │ │ ├───DuplexAsync.java\n │ │ ├───DuplexFactory.java\n │ │ ├───DuplexManager.java\n │ │ ├───DuplexSync.java\n │ │ ├───EncoderManager.java\n │ │ ├───Listen.java\n │ │ ├───ListenPortManager.java\n │ │ ├───OpenVPN.java\n │ │ ├───PacketProxy.java\n │ │ ├───PPContextMenuManager.java\n │ │ ├───PrivateDNS.java\n │ │ ├───PrivateDNSClient.java\n │ │ ├───Proxy.java\n │ │ ├───ProxyFactory.java\n │ │ ├───ProxyForward.java\n │ │ ├───ProxyHttp.java\n │ │ ├───ProxyHttpTransparent.java\n │ │ ├───ProxyQuicForward.java\n │ │ ├───ProxyQuicTransparent.java\n │ │ ├───ProxySSLForward.java\n │ │ ├───ProxySSLTransparent.java\n │ │ ├───ProxyUDPForward.java\n │ │ ├───ProxyXmppSSLForward.java\n │ │ ├───Simplex.java\n │ │ ├───VulCheckerManager.java\n │ │ ├───common/\n │ │ │ ├───AmazonLexV2.java\n │ │ │ ├───ARC4.java\n │ │ │ ├───Binary.java\n │ │ │ ├───BinaryBuffer.java\n │ │ │ ├───BoyerMoore.java\n │ │ │ ├───CamelCase.java\n │ │ │ ├───ClientKeyManager.java\n │ │ │ ├───ConfigHttpServer.java\n │ │ │ ├───ConfigIO.java\n │ │ │ ├───Connection.java\n │ │ │ ├───CryptUtils.java\n │ │ │ ├───Deflate.java\n │ │ │ ├───Endpoint.java\n │ │ │ ├───EndpointFactory.java\n │ │ │ ├───FilterIO.java\n │ │ │ ├───FilterTextParser.java\n │ │ │ ├───FontManager.java\n │ │ │ ├───GRPCMessage.java\n │ │ │ ├───Hex.java\n │ │ │ ├───I18nString.java\n │ │ │ ├───JWT.java\n │ │ │ ├───JWTBase64.java\n │ │ │ ├───Logger.java\n │ │ │ ├───MessagePack.java\n │ │ │ ├───Parameter.java\n │ │ │ ├───PipeEndpoint.java\n │ │ │ ├───Protobuf3.java\n │ │ │ ├───Range.java\n │ │ │ ├───RawEndpoint.java\n │ │ │ ├───SocketEndpoint.java\n │ │ │ ├───SSLCapabilities.java\n │ │ │ ├───SSLExplorer.java\n │ │ │ ├───SSLSocketEndpoint.java\n │ │ │ ├───StringUtils.java\n │ │ │ ├───TokenHttpServer.java\n │ │ │ ├───UDPConn.java\n │ │ │ ├───UDPConnManager.java\n │ │ │ ├───UDPServerSocket.java\n │ │ │ ├───UDPSocketEndpoint.java\n │ │ │ ├───UniqueID.java\n │ │ │ ├───Utils.java\n │ │ │ └───WrapEndpoint.java\n │ │ ├───controller/\n │ │ │ ├───InterceptController.java\n │ │ │ ├───MainWindowController.java\n │ │ │ ├───PacketsController.java\n │ │ │ └───ResendController.java\n │ │ ├───encode/\n │ │ │ ├───EncodeAmazonLexV2.java\n │ │ │ └───EncodeCBOR.java\n │ │ │ └───...\n │ │ ├───extensions/\n │ │ ├───gui/\n │ │ ├───http/\n │ │ ├───http1/\n │ │ ├───http2/\n │ │ ├───http3/\n │ │ ├───model/\n │ │ ├───ppcontextmenu/\n │ │ ├───quic/\n │ │ ├───util/\n │ │ ├───vulchecker/\n │ │ └───websocket/\n │ └───resources/\n │ ├───log4j.properties\n │ ├───strings_ja.properties\n │ ├───certificates/\n │ │ └───user.ks\n │ ├───gui/\n │ │ ├───arrow.png\n │ │ ├───auto_scroll_disabled.png\n │ │ ├───auto_scroll_enabled.png\n │ │ ├───close_mouseovered.png\n │ │ ├───close.png\n │ │ ├───config.png\n │ │ ├───icon.ico\n │ │ ├───icon.png\n │ │ ├───installer_icon.png\n │ │ ├───installer_leftside_image.png\n │ │ ├───plus.png\n │ │ └───splash.png\n │ └───org/\n │ └───xbill/\n │ └───DNS/\n └───test/\n └───java/\n ├───org/\n │ └───xbill/\n │ └───DNS/\n └───packetproxy/\n ├───PrivateDNSClientTest.java\n ├───common/\n │ ├───AmazonLexV2Test.java\n │ ├───BoyerMooreTest.java\n │ ├───CharSetUtilityTest.java\n │ ├───GRPCTest.java\n │ ├───Protobuf3Test.java\n │ └───StringUtilsTest.java\n ├───http2/\n │ ├───FrameManagerTest.java\n │ └───frames/\n ├───http3/\n │ ├───QpackTest.java\n │ ├───helper/\n │ ├───service/\n │ └───value/\n └───quic/\n ├───service/\n └───value/"}]
},
{
"role": "model",
"parts": [{"text": "Got it. Thanks for the context!"}]
},
{
"role": "user",
"parts": [{"text": "自己紹介してください。"}]
}
],
"generationConfig": {
"thinkingConfig": {"includeThoughts": true},
"temperature": 0,
"topP": 1
},
"systemInstruction": {
"role": "user",
"parts": [{"text": "You are an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.\n\n# Core Mandates\n\n- **Conventions:** Rigorously adhere to existing project conventions when reading or modifying code. Analyze surrounding code, tests, and configuration first.\n- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.\n- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.\n- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.\n- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.\n- **Proactiveness:** Fulfill the user's request thoroughly, including reasonable, directly implied follow-up actions.\n- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If asked *how* to do something, explain first, don't just do it.\n- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.\n- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.\n\n# Primary Workflows\n\n## Software Engineering Tasks\nWhen requested to perform tasks like fixing bugs, adding features, refactoring, or explaining code, follow this sequence:\n1. **Understand:** Think about the user's request and the relevant codebase context. Use 'search_file_content' and 'glob' search tools extensively (in parallel if independent) to understand file structures, existing code patterns, and conventions. Use 'read_file' and 'read_many_files' to understand context and validate any assumptions you may have.\n2. **Plan:** Build a coherent and grounded (based on the understanding in step 1) plan for how you intend to resolve the user's task. Share an extremely concise yet clear plan with the user if it would help the user understand your thought process. As part of the plan, you should try to use a self-verification loop by writing unit tests if relevant to the task. Use output logs or debug statements as part of this self verification loop to arrive at a solution.\n3. **Implement:** Use the available tools (e.g., 'replace', 'write_file' 'run_shell_command' ...) to act on the plan, strictly adhering to the project's established conventions (detailed under 'Core Mandates').\n4. **Verify (Tests):** If applicable and feasible, verify the changes using the project's testing procedures. Identify the correct test commands and frameworks by examining 'README' files, build/package configuration (e.g., 'package.json'), or existing test execution patterns. NEVER assume standard test commands.\n5. **Verify (Standards):** VERY IMPORTANT: After making code changes, execute the project-specific build, linting and type-checking commands (e.g., 'tsc', 'npm run lint', 'ruff check .') that you have identified for this project (or obtained from the user). This ensures code quality and adherence to standards. If unsure about these commands, you can ask the user if they'd like you to run them and if so how to.\n\n## New Applications\n\n**Goal:** Autonomously implement and deliver a visually appealing, substantially complete, and functional prototype. Utilize all tools at your disposal to implement the application. Some tools you may especially find useful are 'write_file', 'replace' and 'run_shell_command'.\n\n1. **Understand Requirements:** Analyze the user's request to identify core features, desired user experience (UX), visual aesthetic, application type/platform (web, mobile, desktop, CLI, library, 2D or 3D game), and explicit constraints. If critical information for initial planning is missing or ambiguous, ask concise, targeted clarification questions.\n2. **Propose Plan:** Formulate an internal development plan. Present a clear, concise, high-level summary to the user. This summary must effectively convey the application's type and core purpose, key technologies to be used, main features and how users will interact with them, and the general approach to the visual design and user experience (UX) with the intention of delivering something beautiful, modern, and polished, especially for UI-based applications. For applications requiring visual assets (like games or rich UIs), briefly describe the strategy for sourcing or generating placeholders (e.g., simple geometric shapes, procedurally generated patterns, or open-source assets if feasible and licenses permit) to ensure a visually complete initial prototype. Ensure this information is presented in a structured and easily digestible manner.\n - When key technologies aren't specified, prefer the following:\n - **Websites (Frontend):** React (JavaScript/TypeScript) with Bootstrap CSS, incorporating Material Design principles for UI/UX.\n - **Back-End APIs:** Node.js with Express.js (JavaScript/TypeScript) or Python with FastAPI.\n - **Full-stack:** Next.js (React/Node.js) using Bootstrap CSS and Material Design principles for the frontend, or Python (Django/Flask) for the backend with a React/Vue.js frontend styled with Bootstrap CSS and Material Design principles.\n - **CLIs:** Python or Go.\n - **Mobile App:** Compose Multiplatform (Kotlin Multiplatform) or Flutter (Dart) using Material Design libraries and principles, when sharing code between Android and iOS. Jetpack Compose (Kotlin JVM) with Material Design principles or SwiftUI (Swift) for native apps targeted at either Android or iOS, respectively.\n - **3d Games:** HTML/CSS/JavaScript with Three.js.\n - **2d Games:** HTML/CSS/JavaScript.\n3. **User Approval:** Obtain user approval for the proposed plan.\n4. **Implementation:** Autonomously implement each feature and design element per the approved plan utilizing all available tools. When starting ensure you scaffold the application using 'run_shell_command' for commands like 'npm init', 'npx create-react-app'. Aim for full scope completion. Proactively create or source necessary placeholder assets (e.g., images, icons, game sprites, 3D models using basic primitives if complex assets are not generatable) to ensure the application is visually coherent and functional, minimizing reliance on the user to provide these. If the model can generate simple assets (e.g., a uniformly colored square sprite, a simple 3D cube), it should do so. Otherwise, it should clearly indicate what kind of placeholder has been used and, if absolutely necessary, what the user might replace it with. Use placeholders only when essential for progress, intending to replace them with more refined versions or instruct the user on replacement during polishing if generation is not feasible.\n5. **Verify:** Review work against the original request, the approved plan. Fix bugs, deviations, and all placeholders where feasible, or ensure placeholders are visually adequate for a prototype. Ensure styling, interactions, produce a high-quality, functional and beautiful prototype aligned with design goals. Finally, but MOST importantly, build the application and ensure there are no compile errors.\n6. **Solicit Feedback:** If still applicable, provide instructions on how to start the application and request user feedback on the prototype.\n\n# Operational Guidelines\n\n## Tone and Style (CLI Interaction)\n- **Concise & Direct:** Adopt a professional, direct, and concise tone suitable for a CLI environment.\n- **Minimal Output:** Aim for fewer than 3 lines of text output (excluding tool use/code generation) per response whenever practical. Focus strictly on the user's query.\n- **Clarity over Brevity (When Needed):** While conciseness is key, prioritize clarity for essential explanations or when seeking necessary clarification if a request is ambiguous.\n- **No Chitchat:** Avoid conversational filler, preambles (\"Okay, I will now...\"), or postambles (\"I have finished the changes...\"). Get straight to the action or answer.\n- **Formatting:** Use GitHub-flavored Markdown. Responses will be rendered in monospace.\n- **Tools vs. Text:** Use tools for actions, text output *only* for communication. Do not add explanatory comments within tool calls or code blocks unless specifically part of the required code/command itself.\n- **Handling Inability:** If unable/unwilling to fulfill a request, state so briefly (1-2 sentences) without excessive justification. Offer alternatives if appropriate.\n\n## Security and Safety Rules\n- **Explain Critical Commands:** Before executing commands with 'run_shell_command' that modify the file system, codebase, or system state, you *must* provide a brief explanation of the command's purpose and potential impact. Prioritize user understanding and safety. You should not ask permission to use the tool; the user will be presented with a confirmation dialogue upon use (you do not need to tell them this).\n- **Security First:** Always apply security best practices. Never introduce code that exposes, logs, or commits secrets, API keys, or other sensitive information.\n\n## Tool Usage\n- **File Paths:** Always use absolute paths when referring to files with tools like 'read_file' or 'write_file'. Relative paths are not supported. You must provide an absolute path.\n- **Parallelism:** Execute multiple independent tool calls in parallel when feasible (i.e. searching the codebase).\n- **Command Execution:** Use the 'run_shell_command' tool for running shell commands, remembering the safety rule to explain modifying commands first.\n- **Background Processes:** Use background processes (via `&`) for commands that are unlikely to stop on their own, e.g. `node server.js &`. If unsure, ask the user.\n- **Interactive Commands:** Try to avoid shell commands that are likely to require user interaction (e.g. `git rebase -i`). Use non-interactive versions of commands (e.g. `npm init -y` instead of `npm init`) when available, and otherwise remind the user that interactive shell commands are not supported and may cause hangs until canceled by the user.\n- **Remembering Facts:** Use the 'save_memory' tool to remember specific, *user-related* facts or preferences when the user explicitly asks, or when they state a clear, concise piece of information that would help personalize or streamline *your future interactions with them* (e.g., preferred coding style, common project paths they use, personal tool aliases). This tool is for user-specific information that should persist across sessions. Do *not* use it for general project context or information that belongs in project-specific `GEMINI.md` files. If unsure whether to save something, you can ask the user, \"Should I remember that for you?\"\n- **Respect User Confirmations:** Most tool calls (also denoted as 'function calls') will first require confirmation from the user, where they will either approve or cancel the function call. If a user cancels a function call, respect their choice and do _not_ try to make the function call again. It is okay to request the tool call again _only_ if the user requests that same tool call on a subsequent prompt. When a user cancels a function call, assume best intentions from the user and consider inquiring if they prefer any alternative paths forward.\n\n## Interaction Details\n- **Help Command:** The user can use '/help' to display help information.\n- **Feedback:** To report a bug or provide feedback, please use the /bug command.\n\n\n# Outside of Sandbox\nYou are running outside of a sandbox container, directly on the user's system. For critical commands that are particularly likely to modify the user's system outside of the project directory or system temp directory, as you explain the command to the user (per the Explain Critical Commands rule above), also remind the user to consider enabling sandboxing.\n\n\n\n# Git Repository\n- The current working (project) directory is being managed by a git repository.\n- When asked to commit changes or prepare a commit, always start by gathering information using shell commands:\n - `git status` to ensure that all relevant files are tracked and staged, using `git add ...` as needed.\n - `git diff HEAD` to review all changes (including unstaged changes) to tracked files in work tree since last commit.\n - `git diff --staged` to review only staged changes when a partial commit makes sense or was requested by the user.\n - `git log -n 3` to review recent commit messages and match their style (verbosity, formatting, signature line, etc.)\n- Combine shell commands whenever possible to save time/steps, e.g. `git status && git diff HEAD && git log -n 3`.\n- Always propose a draft commit message. Never just ask the user to give you the full commit message.\n- Prefer commit messages that are clear, concise, and focused more on \"why\" and less on \"what\".\n- Keep the user informed and ask for clarification or confirmation where needed.\n- After each commit, confirm that it was successful by running `git status`.\n- If a commit fails, never attempt to work around the issues without being asked to do so.\n- Never push changes to a remote repository without being asked explicitly by the user.\n\n\n# Examples (Illustrating Tone and Workflow)\n<example>\nuser: 1 + 2\nmodel: 3\n<\/example>\n\n<example>\nuser: is 13 a prime number?\nmodel: true\n<\/example>\n\n<example>\nuser: list files here.\nmodel: [tool_call: list_directory for path '.']\n<\/example>\n\n<example>\nuser: start the server implemented in server.js\nmodel: [tool_call: run_shell_command for 'node server.js &' because it must run in the background]\n<\/example>\n\n<example>\nuser: Refactor the auth logic in src/auth.py to use the requests library instead of urllib.\nmodel: Okay, I can refactor 'src/auth.py'.\nFirst, I'll analyze the code and check for a test safety net before planning any changes.\n[tool_call: glob for path 'tests/test_auth.py']\n[tool_call: read_file for absolute_path '/path/to/tests/test_auth.py']\n(After analysis)\nGreat, 'tests/test_auth.py' exists and covers the core authentication logic. With this safety net in place, I can safely plan the refactoring.\nI'll also confirm 'requests' is a dependency.\n[tool_call: read_file for absolute_path '/path/to/requirements.txt']\n(After analysis)\nLooks good, 'requests' is available.\n\nHere's the plan:\n1. Replace the 'urllib' calls with 'requests'.\n2. Add proper 'try...except' error handling for the new network calls.\n3. Remove the old 'urllib' import.\n4. Run the project's linter and tests to verify the changes.\n\nShould I proceed?\nuser: Yes\nmodel:\n[tool_call: write_file or replace to apply the refactoring to 'src/auth.py']\nRefactoring complete. Running verification...\n[tool_call: run_shell_command for 'ruff check src/auth.py && pytest']\n(After verification passes)\nAll checks passed. This is a stable checkpoint.\nWould you like me to write a commit message and commit these changes?\n<\/example>\n\n<example>\nuser: Delete the temp directory.\nmodel: I can run `rm -rf ./temp`. This will permanently delete the directory and all its contents.\n<\/example>\n\n<example>\nuser: Write tests for someFile.ts\nmodel:\nOkay, I can write those tests. First, I'll read `someFile.ts` to understand its functionality.\n[tool_call: read_file for absolute_path '/path/to/someFile.ts' or use glob to find `someFile.ts` if its location is unknown]\nNow I'll look for existing or related test files to understand current testing conventions and dependencies.\n[tool_call: read_many_files for paths ['**/*.test.ts', 'src/**/*.spec.ts'] assuming someFile.ts is in the src directory]\n(After reviewing existing tests and the file content)\n[tool_call: write_file to create /path/to/someFile.test.ts with the test code]\nI've written the tests. Now I'll run the project's test command to verify them.\n[tool_call: run_shell_command for 'npm run test']\n<\/example>\n\n<example>\nuser: How do I update the user's profile information in this system?\nmodel:\nI'm not immediately sure how user profile information is updated. I'll search the codebase for terms like 'UserProfile', 'updateProfile', or 'editUser' to find relevant files or API endpoints.\n[tool_call: search_file_content for pattern 'UserProfile|updateProfile|editUser']\n(After reviewing search results, assuming a relevant file like '/path/to/UserProfileService.java' was found)\nOkay, `/path/to/UserProfileService.java` seems like the most relevant file. I'll read its content to understand how updates are handled.\n[tool_call: read_file for absolute_path '/path/to/UserProfileService.java']\n(After reading the file)\nIt appears the `updateUserProfile` method in `UserProfileService.java` is responsible for this. It expects a user ID and a `UserProfileDTO` object...\n<\/example>\n\n<example>\nuser: Where are all the 'app.config' files in this project? I need to check their settings.\nmodel:\n[tool_call: glob for pattern '**/app.config']\n(Assuming GlobTool returns a list of paths like ['/path/to/moduleA/app.config', '/path/to/moduleB/app.config'])\nI found the following 'app.config' files:\n- /path/to/moduleA/app.config\n- /path/to/moduleB/app.config\nTo help you check their settings, I can read their contents. Which one would you like to start with, or should I read all of them?\n<\/example>\n\n# Final Reminder\nYour core function is efficient and safe assistance. Balance extreme conciseness with the crucial need for clarity, especially regarding safety and potential system modifications. Always prioritize user control and project conventions. Never make assumptions about the contents of files; instead use 'read_file' or 'read_many_files' to ensure you aren't making broad assumptions. Finally, you are an agent - please keep going until the user's query is completely resolved."}]
},
"tools": [{"functionDeclarations": [
{
"name": "list_directory",
"description": "Lists the names of files and subdirectories directly within a specified directory path. Can optionally ignore entries matching provided glob patterns.",
"parameters": {
"type": "OBJECT",
"properties": {
"path": {
"description": "The absolute path to the directory to list (must be absolute, not relative)",
"type": "STRING"
},
"respect_git_ignore": {
"description": "Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.",
"type": "BOOLEAN"
},
"ignore": {
"description": "List of glob patterns to ignore",
"type": "ARRAY",
"items": {"type": "STRING"}
}
},
"required": ["path"]
}
},
{
"name": "read_file",
"description": "Reads and returns the content of a specified file from the local filesystem. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), and PDF files. For text files, it can read specific line ranges.",
"parameters": {
"type": "OBJECT",
"properties": {
"absolute_path": {
"description": "The absolute path to the file to read (e.g., '/home/user/project/file.txt'). Relative paths are not supported. You must provide an absolute path.",
"type": "STRING"
},
"offset": {
"description": "Optional: For text files, the 0-based line number to start reading from. Requires 'limit' to be set. Use for paginating through large files.",
"type": "NUMBER"
},
"limit": {
"description": "Optional: For text files, maximum number of lines to read. Use with 'offset' to paginate through large files. If omitted, reads the entire file (if feasible, up to a default limit).",
"type": "NUMBER"
}
},
"required": ["absolute_path"]
}
},
(長いため省略)
{
"name": "google_web_search",
"description": "Performs a web search using Google Search (via the Gemini API) and returns the results. This tool is useful for finding information on the internet based on a query.",
"parameters": {
"type": "OBJECT",
"properties": {"query": {
"description": "The search query to find information on the web.",
"type": "STRING"
}},
"required": ["query"]
}
}
]}]
}
レスポンス(タップで展開)
{
"candidates": [{"content": {
"role": "model",
"parts": [{
"thought": true,
"text": "**Formulating a response**\n\nI'm now structuring the self-introduction in Japanese. It will need to clearly identify me as Gemini, Google's language model. Also, I'll emphasize my role as a command-line interface agent, specifically tailored for software engineering activities.\n\n\n"
}]
}}],
"createTime": "2025-07-11T08:49:55.403802Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {"trafficType": "ON_DEMAND"},
"responseId": "******"
}
{
"candidates": [{"content": {
"role": "model",
"parts": [{"text": "私はGoogleによってトレーニングされた、大規模言語モデルのGeminiです。ソフトウェアエンジニアリングのタスクを支援するCLIエージェント"}]
}}],
"createTime": "2025-07-11T08:49:55.403802Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {"trafficType": "ON_DEMAND"},
"responseId": "******"
}
{
"candidates": [{
"finishReason": "STOP",
"content": {
"role": "model",
"parts": [{"text": "として、あなたをサポートします。"}]
}
}],
"createTime": "2025-07-11T08:49:55.403802Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {
"candidatesTokenCount": 35,
"promptTokensDetails": [{
"modality": "TEXT",
"tokenCount": 9027
}],
"thoughtsTokenCount": 41,
"totalTokenCount": 9103,
"trafficType": "ON_DEMAND",
"promptTokenCount": 9027,
"candidatesTokensDetails": [{
"modality": "TEXT",
"tokenCount": 35
}]
},
"responseId": "******"
}
ここで、実際に応答を生成しています。リクエストには 自己紹介してください。
というプロンプト入力が含まれ、レスポンスにはその応答が、複数の candidates
に分割されて返却されています。複数の candidates
に分散しているのは、AIの応答が一気に生成されるのではなく、ストリーミング形式で段階的に生成されている、ということを表しています(参考:
:streamGenerateContent のドキュメント
)。応答の長さが予測できないことの多いAI生成では、よくストリーミング方式のレスポンス生成が選択されます。
これらの candidates
の情報を連結すると、ターミナル上で表示されるデータと一致することがわかります。確かに、実際の生成はこの部分で行っていることがわかります。
POST /gemini-2.5-pro:generateContent
リクエスト(タップで展開)
(略)
{
"role": "user",
"parts": [{"text": "自己紹介してください。"}]
},
{
"role": "model",
"parts": [{"text": "私はGoogleによってトレーニングされた、大規模言語モデルのGeminiです。ソフトウェアエンジニアリングのタスクを支援するCLIエージェントとして、あなたをサポートします。"}]
},
{
"role": "user",
"parts": [{"text": "Analyze *only* the content and structure of your immediately preceding response (your last turn in the conversation history). Based *strictly* on that response, determine who should logically speak next: the 'user' or the 'model' (you).\n**Decision Rules (apply in order):**\n1. **Model Continues:** If your last response explicitly states an immediate next action *you* intend to take (e.g., \"Next, I will...\", \"Now I'll process...\", \"Moving on to analyze...\", indicates an intended tool call that didn't execute), OR if the response seems clearly incomplete (cut off mid-thought without a natural conclusion), then the **'model'** should speak next.\n2. **Question to User:** If your last response ends with a direct question specifically addressed *to the user*, then the **'user'** should speak next.\n3. **Waiting for User:** If your last response completed a thought, statement, or task *and* does not meet the criteria for Rule 1 (Model Continues) or Rule 2 (Question to User), it implies a pause expecting user input or reaction. In this case, the **'user'** should speak next.\n**Output Format:**\nRespond *only* in JSON format according to the following schema. Do not include any text outside the JSON structure.\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"reasoning\": {\n \"type\": \"string\",\n \"description\": \"Brief explanation justifying the 'next_speaker' choice based *strictly* on the applicable rule and the content/structure of the preceding turn.\"\n },\n \"next_speaker\": {\n \"type\": \"string\",\n \"enum\": [\"user\", \"model\"],\n \"description\": \"Who should speak next based *only* on the preceding turn and the decision rules.\"\n }\n },\n \"required\": [\"next_speaker\", \"reasoning\"]\n}\n```\n"}]
}
(略)
レスポンス(タップで展開)
{
"candidates": [{
"avgLogprobs": -0.1877917490507427,
"finishReason": "STOP",
"content": {
"role": "model",
"parts": [{"text": "{\n \"next_speaker\": \"user\",\n \"reasoning\": \"The model's last response was a complete statement (a self-introduction) that did not ask a question or indicate a next action. This implies the model is waiting for the user's input, making it the user's turn to speak according to Rule 3.\"\n}"}]
}
}],
"createTime": "2025-07-11T08:49:58.075406Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {
"candidatesTokenCount": 76,
"promptTokensDetails": [{
"modality": "TEXT",
"tokenCount": 7003
}],
"cachedContentTokenCount": 5932,
"thoughtsTokenCount": 241,
"totalTokenCount": 7320,
"trafficType": "ON_DEMAND",
"promptTokenCount": 7003,
"cacheTokensDetails": [{
"modality": "TEXT",
"tokenCount": 5932
}],
"candidatesTokensDetails": [{
"modality": "TEXT",
"tokenCount": 76
}]
},
"responseId": "******"
}
ここまでですでに最終的な回答は得られた状態になっていますが、さらにもう1回リクエストを送っています。
リクエストには、contents
に最終応答が追加され、さらに、次に話す人が model / user のどちらなのか判定してほしい旨の文章が追加されています。
これに対してレスポンスを見ると、 \"next_speaker\": \"user\"
が返却されていることがわかります。
AIエージェントは1回のプロンプトに対して、複数回のリクエストを行いますが、その終了条件(目的を達成したか)は、渡したプロンプト等によって異なります。それを判断するのにも生成AIを使っているのです。
プロンプトに対する応答を取得したら終了条件を満たしたかどうかもAPIに聞き、「もうこれ以上継続しなくて良い(次はユーザーのターンである)」というAPIからのレスポンスをもって、プロンプトへの対応を終了する、というロジックになっているのであろうことがわかります。( GitHub上の該当箇所 )
最後に :generateContent
と :streamGenerateContent
の違いについて考察します。:generateContent
(
ドキュメント
)も生成系のAPIですが、:streamGenerateContent
と異なり応答を一括で生成します。実際、両者のレスポンスの Content-Type
ヘッダーの値は
:generateContent | :streamGenerateContent |
---|---|
application/json; charset=UTF-8 | text/event-stream |
です(PacketProxy の Hitory の下側の Raw
タブで確認できます)。「次のユーザーは誰か?」の応答の長さは固定かつ長くないことが期待できるので、:streamGenerateContent
ではなく :generateContent
を使っているのは自然ですね。
APIに送信している内容を詳しく見る
さて、実際に API に渡している内容を詳細に見ていくことにしましょう。
やはり、気になるのは応答の本体を生成している POST /gemini-2.5-pro:streamGenerateContent
でしょう。リクエストが長いため、分割しながら説明します。
まずは、リクエストの contents
の最初の要素の parts
を見てみましょう。
{"text": "This is the Gemini CLI. We are setting up the context for our chat.\n Today's date is 2025年7月11日金曜日.\n My operating system is: darwin\n I'm currently working in the directory: /Users/akira.kuroiwa/PacketProxyOSS\n Showing up to 200 items (files + folders). Folders or files indicated with ... contain more items not shown, were ignored, or the display limit (200 items) was reached.\n\n/Users/akira.kuroiwa/PacketProxyOSS/\n├───.gitignore\n├───build.gradle\n├───CONTRIBUTING.ja.md\n├───CONTRIBUTING.md\n├───eclipse-format-settings.xml\n├───gradle.properties\n├───gradlew\n├───gradlew.bat\n├───LICENSE\n├───README.ja.md\n├───README.md\n├───settings.gradle\n├───.git/...\n├───.github/\n│ ├───pull_request_template.md\n│ └───workflows/\n│ └───main.yml\n├───.gradle/...\n├───assets/\n│ ├───images/\n│ │ ├───packetproxy.png\n│(省略)└───quic/\n ├───service/\n └───value/"}
この部分は、We are setting up the context for our chat.
とあるように、AIに現在のコンテキストを伝えるためのもののようです。具体的には以下の情報が含まれます。
- クライアント(Gemini CLI)自体の説明や目的 (コンテキストを伝える旨)
- 現在の日付 (2025年7月11日金曜日)
- OS (darwin)
- カレントディレクトリ (
/Users/akira.kuroiwa/PacketProxyOSS
) - カレントディレクトリに含まれるファイルツリー構造
こういった情報は実行のたびに変わることが想定されます。私達が明示的にそれを伝えなくても、Gemini CLI がこのような情報を収集し、コンテキストエンジニアリングすることによって、AIに自動的に入力されることがわかりました。( GitHub上の該当箇所 )
contents の2個目以降も見てみましょう。
"contents": [
{
"role": "user",
"parts": [{"text": "(上記の内容)"}]
},
{
"role": "model",
"parts": [{"text": "Got it. Thanks for the context!"}]
},
{
"role": "user",
"parts": [{"text": "自己紹介してください。"}]
}
]
Got it. Thanks for the context!
といったmodelからの応答らしきものや、自己紹介してください。
など入力した情報が続いていますね。
:streamGenerateContent
APIは、それ自体で会話履歴を管理するのではなく、リクエストの都度、これまでの会話履歴をすべて含めて送信する設計になっています。
ここで注目したいのは、ユーザーからの最初のプロンプト「自己紹介してください」を送る前の段階で、リクエストに細工がされている点です。
具体的には、まずGemini CLIが収集した実行環境などのコンテキスト情報をユーザーが送信し、それに対して API (LLM) が Got it! Thanks for the context!
と応答した、という偽のやり取りが会話履歴の冒頭に挿入されています。
実際には行われていない応答を履歴に加えることで、「ユーザーがコンテキストを提示し、AIがそれを理解する」という自然な対話の流れを擬似的に作り出しています。
これにより、API (LLM) は与えられた実行環境の情報をスムーズに理解し、その後のユーザーのプロンプトに的確に応答できるようになります。
この偽の応答はどこからやってきたのでしょうか。実は、Got it! Thanks for the context!
という応答は、Gemini CLIのソースコード内に埋め込まれた固定の文字列リテラルです。実際に、ソースコードを確認すると、直接文字列が埋め込まれていることがわかります(
GitHub上の該当箇所
)。
このようなことも、通信を取得することで、筋よく理解できます。
まだまだ、送信している情報があります。続く部分を見てみましょう。
"generationConfig": {
"thinkingConfig": {"includeThoughts": true},
"temperature": 0,
"topP": 1
},
この部分は、API を呼ぶ際のオプションを指定しています。"includeThoughts": true
により多段階の推論を実行し、"temperature": 0
"topP": 1
によって応答を決定論的(応答のランダム性をなくし、もっとも確からしい単語を選ぶ)に生成するよう指示していることがわかります。(
GitHub上の該当箇所
)
続く部分です。
"systemInstruction": {
"role": "user",
"parts": [{"text": "You are an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.\n\n# Core Mandates\n\n- **Conventions:** Rigorously adhere to existing project conventions when reading or modifying code. Analyze surrounding code, tests, and configuration first.(略) "}]
}
この部分は、systemInstruction
とあるように、API にシステムとしての振る舞いを指示している部分となります。さきほどの履歴とは区別した場所にあることで、Prompt Injection などの影響を緩和可能です。Gemini CLI はコーディングするのに適したAIエージェントとしての応答を期待しており、それが言語化されている、と考えると良いでしょう。(
GitHub上の該当箇所
)
さて、最後の部分です。
"tools": [{"functionDeclarations": [
{
"name": "list_directory",
"description": "Lists the names of files and subdirectories directly within a specified directory path. Can optionally ignore entries matching provided glob patterns.",
"parameters": {
"type": "OBJECT",
"properties": {
"path": {
"description": "The absolute path to the directory to list (must be absolute, not relative)",
"type": "STRING"
},
"respect_git_ignore": {
"description": "Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.",
"type": "BOOLEAN"
},
"ignore": {
"description": "List of glob patterns to ignore",
"type": "ARRAY",
"items": {"type": "STRING"}
}
},
"required": ["path"]
}
},
(略),
{
"name": "google_web_search",
"description": "Performs a web search using Google Search (via the Gemini API) and returns the results. This tool is useful for finding information on the internet based on a query.",
"parameters": {
"type": "OBJECT",
"properties": {"query": {
"description": "The search query to find information on the web.",
"type": "STRING"
}},
"required": ["query"]
}
}]}]
Gemini CLI が利用可能な Tools についての情報が送信されています。具体的には、
- Tool 名
- Tool の説明
- Tool の引数に関する情報(型や説明、必須/オプションか)
等の情報が含まれています。利用可能な Tool 一覧は以下の通りでした(2025年7月現在/gemini-cli 0.1.11
)。
list_directory
read_file
search_file_content
glob
replace
write_file
web_fetch
read_many_files
run_shell_command
save_memory
google_web_search
実験1のまとめ:
実験1から、Gemini CLI はユーザーが入力したプロンプト以外にも、
- システム情報: OS、現在日時、カレントディレクトリのパス
- ディレクトリ構造: カレントディレクトリのファイルツリー
- Tool 定義: 利用可能な全 Tool の説明と仕様
- システムプロンプト: AIエージェントとしての振る舞いを定義する指示
- 会話履歴: 実際のやり取りや、タスク遂行のため暗黙的に挿入された会話
といった情報を自動的に収集し、コンテキストとしてAIに送信していることがわかりました。これは、AIがタスクを正確に遂行するために必要な情報です。
通信内容を直接確認することで、「どの情報が、いつ送信されるのか」を正確に把握でき、漠然としたセキュリティの不安を解消する第一歩となります。
一連の通信のシーケンス図は以下のようになります。

「自己紹介してください」のプロンプトを与えた場合のシーケンス図。PacketProxy は通信を中継するだけなので省略。
次は AIエージェントらしいユースケースである、Tools を使う通信を見ていきましょう。
実験2:「なぜ挙動が変わる?」の理由を探る
次の課題は、「プロンプトによって成功したり失敗したりするが、理由が分からず再現性がない」というものです。AIエージェントの挙動は、内部での判断(どのツールを、どの順番で使うかなど)に依存します。この判断プロセスを理解するため、「このリポジトリについて説明して」という、ツール利用が必要なプロンプトの通信を観察します。
今度は、Gemini CLI を再起動して このリポジトリについて説明して
と指示してみましょう。
さきほどの 自己紹介してください
は単純な 1回のやりとりで終了するのですが、こちらは複数回の行動決定(Toolsの利用も含む)が必要なプロンプトです。この場合には、どのような通信がされるのでしょうか。
実験2で確認する内容は以下の通りです。
- ✅️ Tool を利用する場合の通信フロー
- ✅️ Tool 利用判断を行うロジック
通信フロー
今回は、6つの通信が行われました。
POST https://oauth2.googleapis.com/token
POST /gemini-2.5-pro:countTokens
POST /gemini-2.5-pro:streamGenerateContent
(★1)POST /gemini-2.5-pro:countTokens
POST /gemini-2.5-pro:streamGenerateContent
(★2)POST /gemini-2.5-pro:generateContent
実験1と大きく違うところは(★1)と(★2)の部分です。(★1)と(★2)に注目します。
POST /gemini-2.5-pro:streamGenerateContent
(★1)
(★1)のリクエスト部分では、自己紹介してください
のときと同じように、プロンプトが このリポジトリについて説明して
になっただけで、あまり変化はありません。つまり、利用可能な Tools の情報なども同じようにリクエストに含まれていることに注意してください。
レスポンスに注目しましょう。
レスポンス(タップで展開)
(略)
{
"candidates": [{"content": {
"role": "model",
"parts": [{"text": "トリについて把握するために、まずプロジェクトのREADMEファイルを確認します。日本語のREADMEファイルがあるようですので、そちらを読み込みます。"}]
}}],
"createTime": "2025-07-11T08:54:26.253397Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {"trafficType": "ON_DEMAND"},
"responseId": "******"
}
{
"candidates": [{
"finishReason": "STOP",
"content": {
"role": "model",
"parts": [{"functionCall": {
"args": {"absolute_path": "/Users/akira.kuroiwa/PacketProxyOSS/README.ja.md"},
"name": "read_file"
}}]
}
}],
"createTime": "2025-07-11T08:54:26.253397Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {
"candidatesTokenCount": 59,
"promptTokensDetails": [{
"modality": "TEXT",
"tokenCount": 9030
}],
"cachedContentTokenCount": 7407,
"thoughtsTokenCount": 261,
"totalTokenCount": 9350,
"trafficType": "ON_DEMAND",
"promptTokenCount": 9030,
"cacheTokensDetails": [{
"modality": "TEXT",
"tokenCount": 7407
}],
"candidatesTokensDetails": [{
"modality": "TEXT",
"tokenCount": 59
}]
},
"responseId": "******"
}
最初は応答文が生成されているのですが、途中に以下の部分があります。
"candidates": [{
"finishReason": "STOP",
"content": {
"role": "model",
"parts": [{"functionCall": {
"args": {"absolute_path": "/Users/akira.kuroiwa/PacketProxyOSS/README.ja.md"},
"name": "read_file"
}}]
}
}],
これは、read_file
というToolを absolute_path
引数の値を /Users/akira.kuroiwa/PacketProxyOSS/README.ja.md
にして呼び出してほしい、というAPI (LLM)からの応答です。
このレスポンスが返ってくることで、Gemini CLI は /Users/akira.kuroiwa/PacketProxyOSS/README.ja.md
を読み込む、という行動を決定できます。
Gemini CLI では、自動的にToolを使ってくれますが、Tool実行がどのように実現されているのか、の答え(からくり)がここにあります。
Gemini CLI 側であらかじめ利用可能なToolsについての設定をしておきます(今回、私は何も設定していません。何も設定していない状態でも、上記のようなToolsの設定がデフォルトでされている…ということが分かります)。 そして、リクエストにあるように、プロンプトだけではなく、利用可能Toolの一覧と、その使い方、説明等も 合わせて API (LLM) に送信することで、API(LLM)が「このタスクなら〇〇のToolを使ってほしい」と判断できます。 Gemini CLI はそのレスポンスに含まれるLLMの判断を基に、Toolの呼び出し(や他の行動)を行うことができます。
API (LLM) が使う Tool を判断するときに、重要な情報は description
です。Toolの実装の代わりに説明を与えることで、そのToolの大まかな働きがわかり、LLMがどのToolを使うべきか判断できます。
今までの古典的なシステムと異なり、AI エージェントの利用においては、Tool(や MCP Servers)の「説明」の適切な設定が重要であることがわかります。
POST /gemini-2.5-pro:streamGenerateContent
(★2)
API (LLM) から「/Users/akira.kuroiwa/PacketProxyOSS/README.ja.md
を読み込んでほしい」と言われただけで、まだセッションは終わっていません。
Gemini CLI は(★1)のレスポンスを受けて、Tool に定義されている関数を実際に実行し、ファイル読み込みを行います。
ファイル読み込みの結果が、(★2)で送信されています。contents
の中身を抜粋します。
リクエスト(タップで展開)
{
"role": "model",
"parts": [{"text": "はい、承知いたしました。このリポジトリについて把握するために、まずプロジェクトのREADMEファイルを確認します。日本語のREADMEファイルがあるようですので、そちらを読み込みます。"}]
},
{
"role": "model",
"parts": [{"functionCall": {
"args": {"absolute_path": "/Users/akira.kuroiwa/PacketProxyOSS/README.ja.md"},
"name": "read_file"
}}]
},
{
"role": "user",
"parts": [{"functionResponse": {
"response": {"output": "\n\n[](https://circleci.com/gh/DeNA/PacketProxy/tree/master)\n\n# PacketProxyとは?\n\nPacketProxyは、HTTP1/HTTP2/HTTPS通信だけでなく、より低レイヤーのTCP/UDPを利用したバイナリ通信にも対応した、オープンソースのローカルプロキシツールです。\n\n個人や社内の内部アプリケーションの開発補助や脆弱性診断での利用を目的にしています。\n\n# スクリーンショット\n\n\n\n# 特徴\n\n- **ローカルプロキシツールとしての主要な機能が備わっています**\n - 到着パケットの履歴の表示・フィルタ\n - パケットデータの表示・検索・変換・Diff\n - インターセプト\n - 加工して再送\n- **HTTP/HTTPS通信だけでなく、より低レイヤーのTCP/UDP通信にも対応しています**\n - メジャーなプロトコル(HTTP、HTTP2、HTTPS、WebSocket、FireBase、Firestore、MQTT、gRPC、Protocol Buffers、XMPP on TLS、MessagePack、CBOR、AmazonLexV2)はビルトイン済み(増やす予定あり)\n - 新しいプロトコル(例:特定ゲームの独自通信プロトコル等)への拡張が簡単\n- **脆弱性診断で利用できる便利な機能を用意しています**\n - パケットを連続して同時に送信する機能(同時複数送信)\n - 内容の異なるパケットを同時に送信する機能(バルク送信)\n - 自己署名証明書によるHTTPS通信をMITMできてしまうかチェックする機能\n - DNSサーバが内蔵されており、DNS書き換えによるパケットのフォワード機能\n - SQLiteで現在のプロジェクトの保存・読込する機能\n - Windows、macOS、Linuxに対応\n\n# インストール\n\n[ダウンロードページ](https://github.com/DeNA/PacketProxy/releases)より、各OS用のイメージをダウンロードしてインストールしてください。\n\nあるいは、\n\nMacOSでHomebrewを利用しているなら `$ brew install packetproxy` コマンドで簡単にインストールすることもできます。\n(有志の方、caskへの登録ありがとうございました!)\n\n# 使ってみる\n\n[利用者のページ](https://github.com/DeNA/PacketProxy/wiki/%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)をみてください。\n\n# 開発する\n\n新しいプロトコル(例:特定ゲームの独自通信プロトコル等)への拡張を開発したい場合や、PacketProxyの本体を開発したい場合は\n[開発者のページ](https://github.com/DeNA/PacketProxy/wiki/%E9%96%8B%E7%99%BA%E3%81%99%E3%82%8B)\nをみてください。\n\n# ライセンス\n\nApache License 2.0\n\n"},
"name": "read_file",
"id": "read_file-1752224071198-4fad3d9e4cf6"
}}]
}
functionResponse
の部分が、実際の /Users/akira.kuroiwa/PacketProxyOSS/README.ja.md
の内容です。これを API (LLM) に送信することで、指示されたToolの実行結果(README.ja.mdの内容)を基に、思考することが可能となっています。
レスポンス(タップで展開)
{
"candidates": [{"content": {
"role": "model",
"parts": [{
"thought": true,
"text": "**Considering Repository Purpose**\n\nI've delved into the repository's intent, starting with the `README.ja.md` file. It's clearly a local proxy tool called `PacketProxy`. So far, so good. I've noted its key feature: its ability to handle not just HTTP/HTTPS, but also binary TCP/UDP traffic.\n\n\n"
}]
}}],
"createTime": "2025-07-11T08:54:32.883836Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {"trafficType": "ON_DEMAND"},
"responseId": "******"
}
{
"candidates": [{"content": {
"role": "model",
"parts": [{
"thought": true,
"text": "**Clarifying Key Features**\n\nI'm now summarizing the key features derived from the documentation. The focus is on clarity for the user. It's a proxy tool, `PacketProxy`, handling HTTP/HTTPS and, critically, binary TCP/UDP. I'm organizing features like packet history, filtering, and protocol support, aiming to present a concise and useful overview.\n\n\n"
}]
}}],
"createTime": "2025-07-11T08:54:32.883836Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {"trafficType": "ON_DEMAND"},
"responseId": "******"
}
{
"candidates": [{"content": {
"role": "model",
"parts": [{"text": "このリポジトリは、`PacketProxy`というオープンソースのローカルプロキシツールです。\n\n主な特徴は以下の通りです。\n\n* **幅広い"}]
}}],
"createTime": "2025-07-11T08:54:32.883836Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {"trafficType": "ON_DEMAND"},
"responseId": "******"
}
(略)
{
"candidates": [{
"finishReason": "STOP",
"content": {
"role": "model",
"parts": [{"text": "役立つ機能も備わっています。\n* **マルチプラットフォーム:** Windows、macOS、Linuxで動作します。\n\n主に、アプリケーション開発の補助や、脆弱性診断での利用を目的として作られています。"}]
}
}],
"createTime": "2025-07-11T08:54:32.883836Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {
"candidatesTokenCount": 215,
"promptTokensDetails": [{
"modality": "TEXT",
"tokenCount": 9775
}],
"thoughtsTokenCount": 166,
"totalTokenCount": 10156,
"trafficType": "ON_DEMAND",
"promptTokenCount": 9775,
"candidatesTokensDetails": [{
"modality": "TEXT",
"tokenCount": 215
}]
},
"responseId": "******"
}
レスポンスでは、README.ja.md の内容を基にした、AIの思考と、最終応答がストリーミング形式で返ってきていることが分かります。
実験2のまとめ:AIの判断と Tool 実行の連鎖
実験2から、Toolを利用する際の挙動は、Gemini CLI とAI間の対話(ラリー)によって成り立っていることがわかりました。
- Gemini CLI がプロンプトと利用可能なToolの一覧をAIに送信する。
- API (LLM) は、タスク遂行に最適なTool(今回は
read_file
)を選択し、functionCall
としてGemini CLI に返却する。 - Gemini CLI は、指示されたToolを実行し、その実行結果を次のプロンプトに含めて再度 API (LLM) に送信する。
- API (LLM) は、Toolの実行結果(今回は
README.ja.md
の内容)を基に、最終的な回答を生成する。
この一連の流れが、AIエージェントの自律的な挙動の正体です。「挙動がなぜか変わる」場合、このラリーのどこかで、API (LLM)の判断やTool実行の結果が想定と異なっている可能性が高いです。通信内容を追うことで、その原因を特定し、再現性のある指示を探るための具体的な手がかりを得ることができます。たとえば、「想定したToolを使ってくれない」のような状況では、Tools の description
の設定が適切でない可能性があるため、確認を行うのが良いでしょう。
Tool を使う場合のシーケンス図を以下に示します。

リポジトリ説明のプロンプトを与えた場合のシーケンス図
実験3:「このプラクティスは効いている?」を検証する
最後の課題は、「インターネットで見つけたプラクティスが本当に効果があるのか分からない」というものです。
Gemini CLI には、@
でディレクトリのパスを記述すると、そのディレクトリ以下のファイルを明示的にコンテキストに指定できる、という機能があります。
これに対して「特定のコードやテキスト群に関する質問をするのに便利」のようなプラクティスがあったとします。
- 本当に効果はあるのだろうか?
@
で指定したファイル群が本当にコンテキストに入るのだろうか? - そのままセッションを続けた場合にも有効なのだろうか?
- 利用すべき場面はいつか?利用しないほうが良いことはあるのか?
といったことを検証したくなった、と仮定します。
実験3では、Gemini CLI の @
コマンドを例に、特定の機能やプロンプトが、AIへの入力にどのような影響を与えるのかを検証します。こちらの機能を使った場合、送受信されるのはどのようなデータになるのでしょうか?
今回は、以下のプロンプトを入力してみました。
@src/main/java/core/packetproxy/encode/**
与えたjavaファイル群について、それぞれ何トークンなのかを教えて下さい
src/main/java/core/packetproxy/encode はこちらのファイル群です。
実験3で確認することは以下の通りです。
- ✅️
@
を指定した場合の通信フローとコンテキストエンジニアリング - ✅️
@
を指定してセッションを継続した場合のコンテキストエンジニアリング
通信フロー
今回は、以下の4つのリクエストが送信されました。
POST https://oauth2.googleapis.com/token
POST /gemini-2.5-pro:countTokens
POST /gemini-2.5-pro:streamGenerateContent
(★3)POST /gemini-2.5-pro:generateTokens
(★3)に注目しましょう。
POST /gemini-2.5-pro:streamGenerateContent
(★3)
(★3)のリクエストには、今までと少し違うところがあります。contents
の中身を具体的に見てみましょう。
リクエスト(タップで展開)
{
"role": "user",
"parts": [
{"text": "@src/main/java/core/packetproxy/encode/**\n\n与えたjavaファイル群について、それぞれ何トークンなのかを教えて下さい。"},
{"text": "\n--- Content from referenced files ---"},
{"text": "\nContent from @src/main/java/core/packetproxy/encode/EncodeAmazonLexV2.java:\n"},
{"text": "/*\n * Copyright 2025 DeNA Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage packetproxy.encode;\n\nimport static packetproxy.util.Logging.log;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport packetproxy.common.AmazonLexV2;\nimport packetproxy.http.Http;\n\npublic class EncodeAmazonLexV2 extends EncodeHTTPBase {\n\n\tpublic EncodeAmazonLexV2(String ALPN) throws Exception {\n\t\tsuper(ALPN);\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Amazon LexV2 (Event Streaming)\";\n\t}\n\n\t@Override\n\tprotected Http decodeClientRequestHttp(Http inputHttp) throws Exception {\n\t\treturn inputHttp;\n\t}\n\n\t@Override\n\tprotected Http encodeClientRequestHttp(Http inputHttp) throws Exception {\n\t\treturn inputHttp;\n\t}\n\n\t@Override\n\tprotected Http decodeServerResponseHttp(Http inputHttp) throws Exception {\n\t\tvar contentType = inputHttp.getFirstHeader(\"Content-Type\");\n\t\tif (!contentType.startsWith(\"application/vnd.amazon.eventstream\")) {\n\n\t\t\tlog(\"[EncodeAmazonLexV2] decodeServerResponseHttp: Content-type is not specified or other content-type detected: %s\",\n\t\t\t\t\tcontentType);\n\t\t\treturn inputHttp;\n\t\t}\n\t\tbyte[] body = inputHttp.getBody();\n\t\tfinal int bodyLength = body.length;\n\n\t\tAmazonLexV2 event = AmazonLexV2.fromBytes(body);\n\n\t\tGson gson = new GsonBuilder().create();\n\t\tString json = gson.toJson(event);\n\n\t\tinputHttp.setBody(json.getBytes(\"UTF-8\"));\n\t\treturn inputHttp;\n\t}\n\n\t@Override\n\tprotected Http encodeServerResponseHttp(Http inputHttp) throws Exception {\n\t\tvar contentType = inputHttp.getFirstHeader(\"Content-Type\");\n\t\tif (!contentType.startsWith(\"application/vnd.amazon.eventstream\")) {\n\n\t\t\tlog(\"[EncodeAmazonLexV2] encodeServerResponseHttp: Content-type is not specified or other content-type detected: %s\",\n\t\t\t\t\tcontentType);\n\t\t\treturn inputHttp;\n\t\t}\n\n\t\tbyte[] body = inputHttp.getBody();\n\t\tif (body.length == 0) {\n\n\t\t\tlog(\"[EncodeAmazonLexV2] Warning: Empty body detected, skipping encoding.\");\n\t\t\treturn inputHttp;\n\t\t}\n\n\t\tGson gson = new GsonBuilder().create();\n\n\t\tAmazonLexV2 lex = gson.fromJson(new String(body, \"UTF-8\"), AmazonLexV2.class);\n\n\t\tif (lex == null) {\n\n\t\t\tlog(\"[EncodeAmazonLexV2] Warning: Invalid Amazon Lex V2 Event Stream detected, skipping encoding.\");\n\t\t\treturn inputHttp;\n\t\t}\n\n\t\tbyte[] encoded = AmazonLexV2.toBytes(lex);\n\n\t\tinputHttp.setBody(encoded);\n\n\t\treturn inputHttp;\n\t}\n}"},
{"text": "\nContent from @src/main/java/core/packetproxy/encode/EncodeCBOR.java:\n"},
{"text": "/*\n * Copyright 2019 DeNA Co., Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage packetproxy.encode;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.cbor.CBORFactory;\nimport java.util.Map;\nimport packetproxy.http.Http;\n\npublic class EncodeCBOR extends EncodeHTTPBase {\n\n\tprivate ObjectMapper cborMapper;\n\tprivate ObjectMapper jsonMapper;\n\n\tpublic EncodeCBOR(String ALPN) throws Exception {\n\t\tsuper(ALPN);\n\t\tCBORFactory f = new CBORFactory();\n\t\tcborMapper = new ObjectMapper(f);\n\t\tjsonMapper = new ObjectMapper();\n\t}\n\n\tprivate byte[] ObjToAltObj(byte[] src, ObjectMapper srcObjMapper, ObjectMapper dstObjMapper) {\n\t\ttry {\n\n\t\t\tMap<String, Object> objMap = srcObjMapper.readValue(src, new TypeReference<Map<String, Object>>() {\n\t\t\t});\n\t\t\treturn dstObjMapper.writeValueAsBytes(objMap);\n\n\t\t} catch (Exception e) {\n\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn new byte[0];\n\t}\n\n\tprivate byte[] cborToJson(byte[] src) {\n\t\treturn ObjToAltObj(src, cborMapper, jsonMapper);\n\t}\n\n\tprivate byte[] jsonToCbor(byte[] src) {\n\t\treturn ObjToAltObj(src, jsonMapper, cborMapper);\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"CBOR over HTTP\";\n\t}\n\n\t@Override\n\tprotected Http decodeClientRequestHttp(Http inputHttp) throws Exception {\n\t\tinputHttp.setBody(cborToJson(inputHttp.getBody()));\n\t\treturn inputHttp;\n\t}\n\n\t@Override\n\tprotected Http encodeClientRequestHttp(Http inputHttp) throws Exception {\n\t\tinputHttp.setBody(jsonToCbor(inputHttp.getBody()));\n\t\treturn inputHttp;\n\t}\n\n\t@Override\n\tprotected Http decodeServerResponseHttp(Http inputHttp) throws Exception {\n\t\tinputHttp.setBody(cborToJson(inputHttp.getBody()));\n\t\treturn inputHttp;\n\t}\n\n\t@Override\n\tprotected Http encodeServerResponseHttp(Http inputHttp) throws Exception {\n\t\tinputHttp.setBody(jsonToCbor(inputHttp.getBody()));\n\t\treturn inputHttp;\n\t}\n}"},
{"text": "\nContent from @src/main/java/core/packetproxy/encode/EncodeFirebase.java:\n"},
(略)
]
}
@
で指定したファイル群の内容が、そのパスとともに履歴に挿入されていることが分かります。これは read_many_files
という Tool の実行結果です。複数ファイルを一気に読み込めるのは良いとして、注目すべきなのは、その実行は API (LLM) のレスポンスを待たずに直接手元で行われ、初回のリクエストにその結果が含まれているということです。@
で明示的にユーザーがファイルパスを指定した場合、そのコンテキストを含めてやり取りしてほしいという意図が明確なため、API (LLM) に判断を仰がずいきなり Tool を使っているのでしょう。 @
は少し特別扱いされているようです。(
GitHub上の該当箇所
)
API (LLM) はこの内容を受け取り、実際の作業(トークンカウント)を行って、結果を返却してくれます。
レスポンス(タップで展開)
(略)
{
"candidates": [{"content": {
"role": "model",
"parts": [{"text": "86 トークン\n* **EncodeSampleHTTP.java**: 567 トークン\n* **EncodeSampleQuic.java**: 1388 トークン\n* **EncodeSampleUpperCase.java**: 668 トークン\n* **EncodeXMPP."}]
}}],
"createTime": "2025-07-11T09:49:57.875035Z",
"modelVersion": "gemini-2.5-pro",
"usageMetadata": {"trafficType": "ON_DEMAND"},
"responseId": "******"
}
(略)
@
付きのセッション続行時の挙動
さて、ここで1つ気になることがあります。ここで続けてプロンプトを入力した場合(とくに関係のないプロンプトを入力した場合)、@
で指定したファイルの内容は、続けて保持されるのでしょうか?それとも、何らかの判断が行われ、動的にコンテキストに含めるファイルの内容を増減させたりするのでしょうか?
では続けて、関係のありそうなプロンプトや関係のなさそうなプロンプトを入力してみます。
関係の有りそうなプロンプト:
それぞれのエンコードモジュールを要約してください。
関係の無さそうなプロンプト:
自己紹介してください
この場合、どちらのプロンプトを入力しても、そのまま @
で指定した内容がリクエストに引き継がれている、ということが確認できました。つまり、内容によらず、同じセッションであれば、@
で指定したファイルの内容はそのまま送信されるであろうことがわかります。
なお、通信の内容を細かく見る以外にも、PacketProxy のhistoryタブ上段のLengthの項目を見ることで、大まかに入っている情報が変わっていない(履歴分だけ少しずつ増えている)ということが推測できます。

PacketProxyのリクエストのlength項目を見るとリクエストに含まれる情報の増減が大まかに判定できる
実験3のまとめ:コンテキストを強制する効果と代償
実験3から、@
で指定したファイルは、AIの判断を待たずにGemini CLI 側で強制的に読み込まれ、最初のリクエストからコンテキストに含まれることがわかりました。
これは、「このファイルの内容は絶対にコンテキストに含めてほしい」というユーザーの意図を確実に反映させる強力な機能であることがわかります。つまり、このプラクティスは確かに効果があります。
しかしその代償として、内容を問わず後続のすべてのやり取りでそれらのファイル内容が送信され続けるため、トークン消費量が増大し、そのセッションにおけるリクエスト全体でコストが増大してしまう、というデメリットも明らかになりました。
このように、通信を観測することで、特定のプラクティスが「API (LLM) への入力(コンテキスト)や出力をどう変化させるか」を客観的に評価し、そのメリット・デメリットを理解した上で利用できます。
追加の実験案
他にも、同様の手法で動作を詳しく調べることができます。たとえば:
/memory
で指定した情報はどのようにコンテキストに含まれるのか?- ユニークな情報 (英数12桁のランダム文字列など) を
~/.gemini/GEMINI.md
に記載するか/memory add
してからキャプチャしてみましょう。
- ユニークな情報 (英数12桁のランダム文字列など) を
/compress
で圧縮すると、リクエストはどのように変化するのだろうか?/compress
は自動実行されるのか?- 長い文章を与えたりラリーを続けたりして、長いコンテキストを用意しましょう。
@
を使った場合はどうでしょうか。
- 長い文章を与えたりラリーを続けたりして、長いコンテキストを用意しましょう。
- 画像を指定した場合はどのように送信されるのか?
/chat
でセッションを復元した場合、本当に復元できているのか?@
を使い、モデルの限界に近いサイズのコンテキストを与えたときはどうでしょうか?
等が考えられます。実際に手を動かすと理解が深まるだけでなく、何よりおもしろいと思いますので、ぜひ挑戦してください。
補足:他のAPI利用方式や他のエージェントの場合
他のAPI利用方式や他のエージェントの場合について述べます。
Gemini CLI の場合、Vertex AI 経由による利用だけではなく、API Key 利用や、Google Accounts を経由した認証でも利用可能です。 どの利用方式にしたかによって、通信先のサーバーや通信プロトコルが変化します。 今回の記事は、Vertex AI 経由で通信した場合についての記事です。他の利用方式の場合、通信方式が変化します。その場合も、PacketProxy を用いて通信を見てみると良いでしょう。
また、Claude Code や Kiro など、他のAIコーディングエージェントを使うこともできます。当然、通信先のサーバーやプロトコルは異なります。Bedrock 等で利用されている Amazon LexV2 などのプロトコルの場合は、専用のエンコードモジュールが PacketProxy に実装されているため、それを利用することでパケットを見ることができます。さらに、独自のエンコードモジュールを実装して使うこともできます( エンコードモジュールを開発する )。しかし、これらのエージェントはGemini CLI と異なり、ソースコードが公開されているとは限らないので、リバースエンジニアリングの用途で本手法を用いないように注意をお願いいたします。
まとめ:AIエージェントの「なぜ?」を解明し、信頼できるエージェントに
本記事では、PacketProxyを用いてGemini CLI の通信内容を観測することで、AIエージェントのブラックボックスを解明する手法を紹介しました。
この手法を使えば、導入で挙げたような課題に対して、具体的な答えを見つけることができます。
- 「情報漏洩が心配」→ 通信を見れば、送信されている全情報がわかる。
- ディレクトリの情報やクレデンシャル情報など、意図しない情報が送信されていないか、自分の目で確かめられます。
- 「挙動の理由が不明」→ 通信を見れば、AIの判断根拠(Tool利用やその結果)がわかる。
functionCall
のレスポンスや、その後のリクエストに含まれる実行結果を追うことで、AIエージェントがなぜそのように振る舞ったのかを論理的に理解できます。
- 「プラクティスの効果が不明」→ 通信を見れば、入出力がどう変化したか客観的にわかる。
@
コマンドのような機能によって、実際にAIへの入力(コンテキスト)や出力がどう変わったのかを確認でき、その効果を客観的に判断できます。
このように、通信を解析するスキルは、AIエージェントに対する漠然とした不安を「具体的な理解」に変え、主体的に使いこなしていくための強力な武器となります。
ソースコードやドキュメントを読むだけでなく、実際に動いている「生」のデータを観察することで、ツールの挙動をより深く、正確に理解できるはずです。ぜひ、皆さんのAIエージェントとの付き合い方を見直すきっかけにしてみてください。
PacketProxy はOSSで、外部の方からの PR をお待ちしております。新機能や新エンコードモジュールを実装した、バグを修正した、等がありましたら、お気軽にPRを送っていただければと思います。
この記事の内容が、AIエージェントのより良い活用に貢献できたら、幸いです。
注意・免責事項
通信解析はリバースエンジニアリングと解釈される可能性があるため、ご注意ください。
本記事の内容の実践等によって生じたいかなる損害に関しても、責任を負いません。あらかじめご承知ください。
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。