ブログ内のリンクが間違っていたので修正し、Cloudflare にアップロードしなおす、が、反映されない。試行錯誤の記録:
- ブラウザキャッシュを疑う -> そんなん最初に試すわ。
- “Clouflare Caching” を疑う -> Cloudflare Pages は Caching を (直接) は使っていないので、対処のしようがない。
- コンテンツをカラにして deploy し、そのあとオリジナルのコンテンツを deploy しなおす -> コンテンツをカラにするとファイルは消えるが、オリジナルのコンテンツに戻そうとすると古いファイルが復活する。なんなの。
- リンクではない可視な部分を変更して deploy し、そのあと変更した部分をもとに戻して deploy しなおす -> It worked!!!
- 具体的には Hugo の設定でタイトルをかえ、またもとに戻しました。
邪推としては、コンテンツ一致判定 (hashing) のアルゴリズムになんらかの heuristics を使っており、その heuristics にバグがある。ま、タダ乗りユーザなんでワークアラウンドできたのでよしとします。
ちなみに http://localhost:1313/
へのリンクが残っているというちょー恥ずかしい間違いだったので、なんとしても直したかったのだった (たぶん hugo serve のデータがアップロードのち残留してしまった)。
cf. VibeCon - the biggest vibe coding conference! : r/ChatGPTCoding
追記: pages deploy --skip-caching
念の為に コード を読んでいたら pages deploy には --skip-caching
というフラグがあり、ためしたかんじこれで確実に回避できそうである。--help
でリストされるようにしといてくれよ・・・
Roughly every five years, the cost to build a factory for making such chips doubles, and the number of companies that can do it halves. 25 years ago, there were about 40 such companies and the cost to build a fab was about $2-4 billion. Today, there are either two or three such companies left (depending on your optimism toward Intel) and the cost to build a fab is in excess of $100 billion. Project these trends forward another ten years and you can expect a single factory to cost nearly half a trillion dollars, and the number of companies that can do it should drop to less than one.
人類これ以上 CPU 作るのムリなのでは、という気分になるが、
Highly parallel chips designed from the ground up for extremely high defect tolerance could perhaps be manufactured with a vastly lower-quality, and by extension vastly cheaper chip fab.
I’m currently raising some pre-seed funds, aiming to build high-performance, high-efficiency, general-purpose CPUs designed from first principles. I’m also increasingly convinced that, with the right compromises and specialization, a startup-scale fab may be viable and is at least well-worth the experiment.
という求人への長いフリなのだった。Cerebras が似たようなことを言っていましたね。
100x Defect Tolerance: How Cerebras Solved the Yield Problem - Cerebras
字数の大半はチップ細微化の不可能性を長々と説明していて、そこは面白い。
NSF の funding が切られたという話。アカデミアの皆様はヨーロッパなりアジアなりにお越しください。Hahaha.
The deal was simple: We’ll pay you 20-50% below market rate, but in exchange, you get stability, reasonable work-life balance, and most importantly, no layoffs. This wasn’t written in any employee handbook, but everyone understood it. It was the Microsoft way.
あの日までは南の方で Google 社員も似たような幻想を抱いていた旨をお知らせします。
そんな Pact を反故にした社長のお言葉ですが・・・
By every objective measure, Microsoft is thriving—our market performance, strategic positioning, and growth all point up and to the right. (…) And yet, at the same time, we’ve undergone layoffs.
This is the enigma of success in an industry that has no franchise value.
きみたちめっちゃ franchise value で生きてるじゃんか!!検索みたいにエーアイに殺される心配もないじゃんか!!何いってんだ!!なでらっち、最近ちょっと Zuck 氏みたいなサイコパスになってきてない? Sam Altman にフラレて性格変わっちゃったのかい?
Django 生誕秘話、という以前に Simon Willison キャリア振り返り講演だった。なぜ Datasette みたいなもを作っているのか不思議だったけど、一時期新聞社で働いていたのだね。
ゼロ年代の香り漂ういい(おっさん向けの)話でした。
家族帰省中につき家に独り身なので家事雑用食事などをしつつ無限に音声コンテンツ (podcast, audible) を消化していたが、doomscroll に似たダメさがあることに気づいた。アタマのスペースが他人の言葉に埋められ、思考停止してしまう。
20 世紀によく見られたつけっぱなしのテレビに似ているが、一面ではよりたちが悪い。テレビ(地上波)というのは必ずしも自分の関心との相関が高くないので、無視しやすいし、大半の時間は無視されている。一方でインターネットの音声コンテンツは自分が楽しめそうなものをより好んで選ぶから、退屈さが低い。なので無視しないし、できない。
イヤホンで聞くと、また一層よくない。コンテンツが常に環境音に勝る。なおさら無視できないし、外界の刺激から切り離される。
あるとき、イヤホンをつけた瞬間にピタリと考え事が止まって脳がコンテンツ待ち状態に入ったのに気づいた。何も聞く前からすでに脳死している。
また別のある日、聞いていた podcast でホストの一人が「壊れたイヤホンを修理に出したら返ってくるまで一週間かかって、その間はまったく miserable だった」と愚痴ていた。依存じゃん。そして自分も一緒じゃん。
そこでまずイヤホンをやめて小型スピーカーから音を出してみた。生活音が混じって生活を覆い隠す感じがなくなったのはいいが、話の内容が気になるのに聞こえにくいのはそれはそれでストレス。気になるコンテンツが流れている時点で負けているのかもしれない。
そこで音声コンテンツ・・・というか podcast および audible をやめて音楽をスピーカーから流してみた。これは、いいんじゃないか。生活音でかき消されても気にならないし、脳の占有率も低い。端的にいうと podcast を聞きながらの考え事はむずかしいが、音楽を聞きながらはさほど問題ない。歌詞が気になることはゼロじゃないけれど、人の話よりは桁違いに平和。しかも音量を絞ることで attention budget を減らせるのである!べんりー。
ただ年をとったせいか「好きな音楽」というものが枯渇しているので、そこはなんとかしたい。もっとも家族が帰ってきたあと音楽をかける時間があるのかは不明。暇さ固有の問題なのかもしれない。
Ads-blocker はさておき、モバイルで拡張を使えるのはいいな。
拡張の API は簡単っぽい。
しかし (persistent な) sideload はできない。これは Chrome と一緒。残念。
Android アプリは sideload できるのにブラウザ拡張が sideload できないの、ほんと残念だよな。デスクトップのセキュリティモデル (悪意あるアプリが勝手に拡張を sideload できる)に合わせているからなわけだけれど。ブラウザのこうした野良プログラマビリティの低さは自由を殺していませんか。言いがかりかもしらんけど、せめて beta channel では OK とかのフラグで穴をあけらえるとか、妥協をしてほしい (unbranded build ではできるらしいが、それは不便すぎです)
…と思ってチャットに聞いたところ, web-ext コマンドは sign
ができるらしい! へーそれでいいのでは? そのうち一度気合をいれて Firefox 再挑戦しないとな。
Federighi said he was more concerned the public would believe Apple was making too many compromises to get the software running on iPhones, the people said.
secret sauce を隠したいのかと思いきや、出遅れている事実を隠したいという判断なのか…
なぜあと一ヶ月待たない・・・うっかりリークを織り込み済みということだろうか・・・
わたくしは出荷前ラッシュでわりかし猛然と働き 降ってきた主要なバグを全部直したのでプチ燃え尽き気味。本日はスローに働いておりますが、本当はこういうタイミングこそ色々仕込み作業や借金返済をしないといけないのだよねー。しかし借金=作業バックログをみたらいっぱいありすぎてどんよりしてしまう。まず作業優先で放置してしまったこの借金リストを整理したいと思います。やれやれ。
さて今日は core を眺めようねー。
Code Assist
- CLI 側からよくアクセスされていたのは GeminiClient. こいつは core 側の色々な機能の facade だと思えば良さそうである。
- Facade されているオブジェクトの一つ ContentGenerator インターフェイスを見てみると・・・異なる API endpoints の差分を吸収している。具体的にはなぜなら API キーは GCP を使う勢は普通の Gemini API が使えるが、Google Sign-in 勢は code assist という別の API に依存しているからである!わー大変残念。
- Code Assist というのは VS Code などのプラグインで、全然話題にならなかった。一方
gemini-cli
はそれなりに buzz り、その一因は google sign-in で使える手軽さだったと思うので、統一 API の登場をまたず重複を受け入れる判断はビジネス的には正しかったと言えよう。絵に書いたような tech debt のケーススタディーとして記憶しておいてあげます。がんばった。
- Code Assist API/SDK というのは公式には存在しないので、types.ts を見ると色々なものを社内からコピーした様子が伺えて趣深い。(なお公平を期すために書いておくと、多くの型情報は genai SDKから再利用されている。)
- いやーがんばってるね。
GeminiChat
- さてゴシップでそれた脇道から復帰し続きを読むと・・・
ContentGenerator
は GenAI SDK の API の subset だということがわかる。つまり Code Assist を公式 API の signature にあわせる bridge だった。
- もう一つのメジャーなクラスは GeminiChat. これは ContentGenerator を wrap してオンメモリの history を保持したりする。ところで JS いつの間にか
yield
が入っていたのだね。チャットの結果はストリームなので大活躍している。
- しかしこんなにコードいっぱい書かないとダメとは随分 low-level だな・・・とおもっていたら、
DISCLAIMER: This is a copied version of ... with the intention of working around a key bug
とのことで、普通は SDK を使えば良い。
- さて GeminiClient に戻ると、
--full-context
というフラグをつけるとディレクトリのファイルを全部突っ込む機能があり、その処理がある。1M token 贅沢に使ってこうな、という話か。
- あとは built-in のツールを登録したりとか。
prompts.ts
- お、System Prompt など各種 prompt の定義された prompts.ts があった!見どころの一つ(のはず)。
- 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.
- シレっと Flutter 推すのやめてくれませんかね・・・
- Parallelism: Execute multiple independent tool calls in parallel when feasible (i.e. searching the codebase).
-
そんなことできるんだ!たしかにサーチが並列に動いてる時あるよな。実装みておきたいところ。
-
few-shot 的なものは <example>
タグで囲んでいる。へえ。
-
plan を立てろと序盤に書いてあり、Plan 提案例が <example>
に入っている。 しかしそこでは plan を箇条書きして許可を求めあとは agentic に動いてしまうので、ステップごとに一旦止まるとか計画をファイルに書くとかしたい場合は GEMINI.md
で上書きしないとダメそうだな。
- が、よくみると環境変数
GEMINI_SYSTEM_MD
を使うと .gemini/system.md
or お好みのパスから上書きできるらしい。いいじゃない!自分の Obsidian helper はコード関係の system prompt まったく必要ないからな。
-
そのほかヒストリ圧縮機能のプロンプトもここにある。君はツールじゃないのかい? とおもったが、history のような内部構造はツールからはアクセスできないな。
-
さて GeminiChat に戻ると generateEmbedding()
なんてのがあるね。何に使うだろうか -> 使われていない。SDK コピペの残骸か。
-
GeminiChat は切り上げ他のファイルも眺めましょう。
-
はて、ところで昨日は gemini-cli はクライアント・サーバだと書いたけれど、全然クライアントサーバじゃないね。UI とロジックが切り分けてあるだけで。まあその方が自然だと思うけれど。
logger.ts
- core/src/index.ts をじっとにらみ・・・
- logger.ts いってみっか。
- メッセージをファイルに書きます。次回起動時に続行できます。
- へーと思い
.gemini
にある logs.json を見ると、ユーザ(自分)のメッセージしかない。はて?コードを確認すると、たしかにユーザのログしか書いてない。なんか嬉しいのそれ?
- というと、user input の UI から “↑” キーで履歴を戻るためのものらしい。それめっちゃ UI の機能じゃん・・・。
- それとは別に、オンメモリのフル履歴を checkpoint する機能もこのファイルに定義されている。それ全然別物だろ・・・ダメだなこのコードは・・・。
sendMessageStream
- 次に turn.ts. これは Chat へのメッセージ送信を微妙なかんじにラップする
Turn
クラスを定義する。 Turn.run()
メソッドが生えている。
- ここでは tool call をハンドル…はせず、tool call request を抜き出して呼び出しもとから使えるようラップする。
Turn
は client.ts の GeminiClient.sendMessageStream()
メソッドから呼ばれている。そしてよく見るとこの sendMessageStream
がいわゆる agentic loop ですね。
Turn
を介してリクエストを送る。すると Tool call が返ってきたり来なかったりするので、それを必要に応じて処理する。
- このコードからわかることは、ツールの並列呼び出しはモデルがネイティブにサポートしているのだね。複数の tool call request が返ってくる。
- というか、tool call にせよその他の返事にせよ、全部
ServerGeminiStreamEvent
として yield され、呼び出し側にストリームされる。(つまり sendMessageStream()
は generator である。)
ServerGeminiStreamEvent
は turn.ts に定義されている。
- これが client.ts の呼び出し側から参照されるんだから依存関係はグチャグチャだな・・・。
- したがって tool call をハンドルするのは GeminiClient の利用者である。しかしどうやってサーバに結果返すの?
- というのを調べるべく、長い旅が始まります。
- Tool call のための入口は
CoreToolScheduler
です。
- coreToolScheduler.ts
- そしてついになる UI 側の useToolScheduler.ts というのがある。
- Core が tool call の状態遷移を管理して色々なイベントを発行し、UI 側がそのイベントに応じて仕事をして返す、というかんじ。
- 仕事とは、UI の表示のみならず、実行許可の確認などもある。
- そういう確認がおわるとツールを実s行し、全部のツールの実行が終わるとコールバックを呼びます。
- ツールは tools.ts にインターフェイスがあり、tool-registory.ts に登録される。built-in tool はどこか最初の方で登録されていた気がする。MCP はどうなのかな?
ToolRegistory
は初期化時に Config を呼んで MCP から tool を登録する。
- そのコードは mcp-client.ts で、これは MCP SDK (というのがある) を gemini-cli 内の Tools のインターフェイスにつなぐという仕事をしています。めっちゃ相互依存してて失神しそうなひどいコードだが、これが若さの勢いなんだよ。
- なお MCP 標準 は MPC server が tools 以外にも色々なデータなどを提供できることになっているが、みたかんじ
gemini-cli
は tools しかサポートしていない。まあ本家たる Claude Desktop でも実装されてない機能があるとどこかで読んだので、そういうものなのでしょう。
- まあ MCP まわりにそんなに驚きはないね。あっても困るが。
checkNextSpeaker
さてツールの結果が出揃ったら(あるいはツールが必要なかったら) dMessageStream は checkNextSpeaker
というのを呼びます (nextSpeakerChecker.ts。)
- というのも、LLM からの返信は必ずしもユーザに戻るわけではなく、たとえばツールの結果をうけて LLM が仕事を続行したいかもしれないわけだ。
- で、どう実装されているかというと、Gemini Flash に訊きます!たしかに NLP 得意だよな君たち!
今日はここまで。というかだいたい気が済んだので code reading は終了。これで何らかの疑問が生じた時にコードを眺めて調べるくらいはできることでしょう (React 部分除く。つまり半分はわかってない。Sigh.)
後半感想:
- コードは悪く言えば雑、良く言えば勢いがある。なんらかの巨大な野望に向かって慎重にデザインして巨大建築を作る・・・という感じはゼロで、競合にキャッチアップするためにちゃちゃっと実装してる雰囲気。
- 良くも悪くも伝統的な “Google っぽさ” はゼロ。
- それは、いいんじゃないですかね。野望はモデルの方にまかせておけば。この大企業にジャカジャカ雑コードを書く余地が残っているとは思わなかったですよ。
- JS の async/await/generator がバリバリに使われており、TS でイベントの型をきちんと定義したりもしており、型付非同期言語の良さが生きているなと感心する。
- Ink は差し置いても、これを TS 以外で書くのはそんなに嬉しくない気がする。Rust … がどうかはしらないが、たとえば Python だと辛いんじゃないか。async/await/generator 全部あるけど全部出来が悪いからな。
- まあ、これは Claude Code を作った人たちが偉かったね。Codex は Rust rewrite をしているけど、どうかねー。
- CLI だから作るのは割と簡単なのかなとおもったが、全然そんなことはなくてコードは結構いっぱい書かれていた。というのは、
- CLI というよりはターミナルアプリなので、UI は割と入り組んでいる。(さわればわかりますね。Claude Code も同様。) もちろん「クラウドのサーバとブラウザのJS」という分離はないので、そこはラクなはず。
- モデルの制限とか SDK のバグとかが結構あるが、直るのを待ったりせずテキトーにワークアラウンドしているケースが多い。このへんは現在進行系の分野ですねーというかんじ。
- UI の要件が色々あるので、コアも高尚な「ドメインモデル」というより UI 部分を event stream consumer および callback に追い出したループ、みたいになっている.しかも flat なループではなく、ツールとかの都合で結構ネストしたりして大変。もうちょっとなんとかならなかったのかと思わなくもないが、そこを勢いで押し切れるのが JS+TS の力のような気もする。
- こうした現実を考えると、今の所 model agnostic にいいものを作るのは大変そう。これは前にも思ったことだけれど, まだ色々荒削りなので抽象化をするのは早いってことじゃないかな。cline とか goose とかがんばってるけれど、しばらくは claude code なり gemini-cli なり model 固有のツールを使うほうが現実的に思える。
- もっともツールを作る人たちは今のうちから作っておかないと分野が成熟した時に出遅れてしまうので、開発を進めていくのが悪いとも思わないけれど。
後発ゆえの必然とはいえオープンソースにしたのは偉かったね。ある程度機能が固まってきた暁にはつよつよエンジニアを連れてきて tech debt を返済のうえ素敵な client-side agentic platform に仕立てていっていただきたいものです。
個人的には「エージェントアプリつくるのこういうかんじかー」と大変勉強になりました。個人がおもちゃとして agent をつくるなら、もっと用途を限定してデザインを簡素化する方法を考える必要があることでしょう。
gemini-cli
のコードをみて、思ったよりコード書きには特化していないことに気づく。ためしにチャットクライントとして使ってみると、そんなに悪くない。
自分は以前からチャットの結論をまとめてどこかに保存しておきたいと思っていた。生のチャット履歴は back-and-forth があって若干読みにくいので、結論だけをどこかに書いておいて欲しい。あと履歴がウェブアプリの中に閉じられているのも気に入らない。さがすのめんどい。まとめ保存 MCP とかが必要なのかとぼんやり考えていたが、よくわからん。というか Gemini Web は MCP なんてない。MCP のある Claude Desktop は Linux 版がない。
ふとターミナルを開き Obsidian の vault に移動して gemini
を使ってみる。GEMINI.md
には「『結論をまとめろ』と言ったら WhatAiSays
ディレクトリに markdown でファイルをかけ」と指示しておく。gemini-cli は(当然)ファイルの読み書きができるので MCP は不要。Jan とちがって最初からウェブ検索もついている。
いくつか試してみると、なかなかよい。手元にファイルが残るのが便利。追加で調べて欲しいことがあったら会話を続けて、さいごに「まとめを更新して」といえば良い。
大きな欠点としては(これは Linux だからかもしれないが) ターミナル + Ink での日本語入力がほぼ不可能なことで、そこは仕方なく英語を使う。でも日本の調べものをしたい時はやや不便。「日本語で検索して」と頼み、まとめも「日本語で書いて」と頼む。
Gemini Web を完全に置き換えたいとは思わないが、ある程度の量のある調べものには CLI を使っていきたい。あと MCP もこの用途で便利に使えるもののがないか探してみるとしよう。Claude Code でも同じことはできそうだが、別にコードを書くわけではないので Gemini でも困らないかな。