LLMアプリをLLMを使いながら作ってみた
これまでの自分の経験
- GitHub Copilot は少しくらい使っていたが、モデルを選んだり最近の機能はそんなに使い込んでいなかった
- Cline はこの着手の前週使ったのが初めてでえらく感動した
- チャット形式になった LLM のサービスではなく剥き出しの LLM を直接使うコードを書くのはサンプル以外だと初めて
くらいの LLM 初心者かつ AI コーディング初心者。「会話」ベースではよく使っているけど、コードを書かせることはほとんどしていなかった。
できたもの
- wtnabe/gemini-app-lobber: A help tool for deploying GeminiApp as google apps script library
- wtnabe/gas-agentic-tools: Agentic Tools as Google Apps Script Library
Google Apps Script の Library として Gemini を利用して何かを実現するための土台になるようなもの。
実際にできることとしては今のところメールを要約してくれるくらい。ただし、一応メールの検索条件もプロンプトも Library を利用する側のコードから与えることができる。
実は GeminiApp というライブラリはすでにあって、ただライブラリとして「共有」はされていなかったので、これを共有可能なものにしやすくするツールも用意した。それが gemini-app-lobber の方。
困っていたこと
1月か2月辺りから Gmail の通知がやたら増えていて本当に困っていた。Gmail は priority inbox を利用していて、important 判定されたメールだけが通知されるようにしていたし、その時期に受信するメールが特別増えるような何かをやったわけではないので、単に
Gmailのimportant判定がおかしなことになった
という意味になる。Gmail の important 判定について直接介入することはできないので、対策としては
- 通知の必要ないメール群に対して明示的に通知されないように filter を設定する
- そもそも通知設定を切る
くらいしかできない。Gmail の通知設定を切るのは事情があって難しくて1、じゃあ通知対象となるメールの数を頑張って減らすか、となるが、そうすると一方で
通知されないように頑張れば頑張るほど埋もれるメールが増える
という課題もあるので、じゃあ
- あとでまとめて見やすいように「要約」してあればマシかな
と考えた。これなら LLM を利用するアプリのお題としてもそんなに悪くないんじゃないかと感じた。
※ あとで気づいたけど、手元に同じく mail summarizer という2年前に初期化したプロジェクトが存在していた。通知爆発する前からどうにかしたかったらしい。中身は何もなかった。アーキテクチャはやはり Google Apps Script だった。
やったこと
2025-03-08 から
- Cline + Claude 3.7 Sonnet で最初に指示可能だったことを指示して全体的なコードを生成
- デバッグしやすさと API call の節約を考えて以下のように機能を担う下のレイヤーをスイッチできるように考えた
- summary を作成する処理は単に truncate する処理と差し替え可能に
- 要約結果をメールとして自分に送信する機能は欲しいが、デバッグしやすさを考えて console 出力と差し替え可能に
一応 Cline は初めてじゃなくて前の週にも触ってたので要領は分かってきていて、ある程度ルールを書きながらコードを「作ってみた」(書いてないから)。
これが見事に面倒を生むことになるが、それはもう少しあとで。
最終的には 人力 + Cline + GitHub Copilot のチャンポンで完成させている。
プラットフォームはGoogle Apps Scriptに
これは
- mhawksey/GeminiApp: GeminiApp is a library that allows integration to Google’s Gemini API in your Google Apps Script projects. It allows for mutli-modal prompts, structured conversations and function calling
の存在を知っていたから - とりあえず Gmail の扱いはいちばん楽だから
- Google AI Studio の Gemini も GAS も無料から使えるので個人でも使い始めやすいし、業務でも効果的に使えるから
- clasp を頼ればコマンドで実行もできて Cline との相性もそこまで悪くなさそうだから
辺りが理由。(とりあえず Cline から実行して結果を見てほしかった。)
この選定はそんなに悪くなかった。Script API と clasp の使い方も Cline + Claude 3.7 Sonnet は十分実用的な知識を持ち合わせていたし、なんとかなる。
最初にある程度ルールと構成を決めて作らせた
困っていたことの対策として考えて基本的な流れは ETL みたいになるので、
- メールを取り出す部分 ( collector → のちに消滅)
- (一通一通取り出して)要約する部分 ( summarizer )
- 要約結果を整える ( summary formatter )
- 出力する部分 ( summary writer )
に分けて、やりたいことを指示。概ねやりたいことを出力させることはできる。一部
GmeilApp#sendEmail()
で頑なに戻り値に status が返ってくると信じ込んでいる
とか、誤った知識をもとにしたコードはあるけど、動くことは動く。正常に動く分には戻ってこない status に期待しても void ではないから問題はない。
ただ、
- 処理の流れを維持する人と
- その処理の中身を担う人
という分け方をうまく伝えることができず、例えば Summarizer で Gemini / Truncate を切り替える、Writer で Console と Gmail を切り替える、みたいな処理が見事に巨大な if になってしまった。これは見本を見せた方が早いかなと思って諦めて、とりあえず動くまでは AI の作りたいように作らせた。
動くようになるまでとそこから公開できるようにするまで体感2倍は違う
てな感じでなんとか翌週には動くところまで辿り着いた。ただ
- 大まかにレイヤーは分かれているとは言え、レイヤーの中でそのまま分岐しているので、例えば処理を担う人を追加したい2場合はまた分岐が増える
- カスタマイズ可能な作りも厳しい
- 自分の都合に合わせた生々しいエントリポイントとプロジェクトレベルでの分離はこのままでは無理
ということで作りとしてはいろいろよくないので、頑張ってリファクタリングしていくことになった。ここからはもう地味な作業。もともとはそれっぽくテストコードもあったけど全部無視して、
- JSDoc annotation で型を定義
- ちょっとずつコードを分離しながら全体の動作が壊れていないか確認
- エントリポイント側からカスタマイズのコードを差し込めるようにする
のをやっていく。
- すると当初想定していた分け方だけだと不十分だと気づく
- じゃあもう一度やり直し
みたいなことをくり返してちょっとずつちょっとずつ変えていく。こうなってくると
- 構造を決める、構造を変更する部分は直接手で書く
- 決めたあとにコピペ3はAIに手伝ってもらう
みたいな感じで、動かしながら設計を導出していく感じになった。終盤は Cline でいちいち指示を出すのがダルいので主に GitHub Copilot に手伝ってもらった。
あとこの辺ではもう clasp run で動かすみたいなのはスッパリ諦めてた。別に Cline に分かってもらう必要ないし、clasp run では function から return されたものが console に出力される格好になるんだけど、それが扱いやすいサイズや形になっているとは限らないので。
やりながら従来と考え方の違いが出るなーと感じたのが
プロンプトに要約もとのメールを与える部分
で、
- 対象のメールをまとめてプロンプトに差し込む方が利用効率はいい(Geminiはトークン制限がだいぶ緩い)
- すると今までのようにできるだけ小さい単位でメソッドを呼ぶ動きと異なる格好になる
けど、
要約処理全体の流れを担うコードからは同じように呼び出せてほしい
と思った時。なるほどなーとなった。まぁ、当然多すぎるメールは制限に引っかかるので、どういう単位で動かすかは
今のところ使う側がGeminiの制限に引っかからないように使う
という方針にしてある。(ひどい)
で、直接 I/O を担うところと担わないところを分け、I/O を担わないところからテストを足して固定していくような形で、いったん期待通りの切り替え、期待通りのカスタマイズができるところまでなんとか辿り着いた。
最後はドキュメント。これもいったんバァーッと書いてもらってガシガシ削るような格好になった。
Geminiをプログラムから呼び出す
ゆーてこの部分は実はそんなに時間掛けてない。ほとんどは普通のアプリケーションの構造の話で、次に Google Apps Script 固有の部分、LLM を利用するのに掛けた時間はかなり少ない。
- Gemini を利用する方法が複数ある
- Google AI / Vertex AI (AWS Bedrock とか調べてない)
- Gemini API の利用方法として Google AI の API key を利用する方法と Google Cloud Project の Vertex AI を利用する方法がある
- それぞれに Studio があり、Studio は単なるチャットインターフェイス以上の機能がある。すごい
- 実は LangChain 要らない
- なんか AI と言えば LangChain ! みたいな感じの情報が溢れてるけど、イマドキの LLM は token の制限もだいぶ緩いし、かつて必須と呼ばれたノウハウが案外要らなくて普通の Web API を呼び出すプログラミングになっている。助かる
- かつて JSON モードと呼ばれていたものが今は Structured Outputs として整理されており、これが ChatGPT にも Gemini にもある(API は若干違う)。コードから呼ぶ場合はこの Structured Outputs を使うと扱いやすい4
- やはりプロンプト次第感がすごい。別コンテキストの自然言語のように見せかけた言語が増えた感じ。しかも LLM が知っているであろう知識を一度引き出して、それを使いこなしていく取り組みが必要になってきそう
- そして Structured Outputs を使うので LLM との I/O の部分だけで必要な知識がギュッと濃密になる
- LLM 自身にプロンプトを作らせるのは単純に勉強になる
- プロンプトを動的に組める機能を外から利用できるようにすると案外考えることが多い5
個人的には生成 AI 関係を調べるとやたら出てくる LangChain から妙に用語が一気にたくさん出てくるのに辟易していたんだけど、今となっては単なる Web API プログラミングになっていることに気づけたのは大きな収穫。こういうのでいいんだよ感。
参考
- Clineに全部賭ける前に 〜Clineの動作原理を深掘り〜
- 2025年の年始に読み直したいAIエージェントの設計原則とか実装パターン集
- Clineを利用した開発が超快適なので、使っている.clinerulesを解説します
- $100燃やして分かったClineのTips
- Structured Outputs - OpenAI API
- Generate structured output with the Gemini API | Google AI for Developers
感じたこと
ルール作り重要
- ルール作りは我慢が必要
- 例えば JavaScript Standard に従うとして、どういうツールでそのチェックを実現するかまでちゃんと書くこと
- クソデカ try-catch まみれ、if のネストまみれ、変数の書き換え、思いつくさまざまな「良くないコード」を生むので、一つ一つ丁寧に除外する(ゼロにはならない)
- 上記の複雑さは処理の単位をどのように分けるかという設計の観点でもあるので、そこもルールが必要6
- メジャーな styling ルールに寄せておくと楽はできる
- 自分は JavaScript Standard に寄せているので、これは指示一つでだいたいイケる
※ 生成した結果に対して人間が修正を加えてルールの見直しもエージェントに行わせるという手法があって、これはとても有効そうだけど今回は「まず動くものを作り切る」のを目標にしていたので、そこまで凝ったことはしていない。
書きすぎ問題
- 動作はする。でも良い設計よりもコードを生み出す方に向いている感じがする。多い。長い
- テストコードもほとんどホワイトボックスじゃね?って感じになるし mock まみれにしたがる
- ただログを出力しまくりたがる部分だけはまぁまぁよいかもと思った。特に GAS はどうしても runtime が独特で放っておくと採れる情報が足りなくなるので。ただこれも I/O を担わない純粋な加工や判定の場合は冗長すぎるのでそこもルール化すべし
タスクの切り方重要
- これは「今のところ」の意味と(恐らく)「長期的に」の両方の意味がある
- LLM を利用するコンテキストで言う(推論や分類などの)タスクへの理解度は比較的長期で意味を持つ。これは現在の LLM の特徴なので
- 「開発のタスク」という意味で小さい単位での切り出しが重要なのは人間に対して依頼する時や、自分自身が commit や merge の単位をどう切るかの話にも通じる部分で、これは長期的に意味を持つ
- 最後に現在の LLM を利用したコーディングエージェントの動作に依存する部分で、今のところ「生成だけが異常に得意」なので、うまくコントロールするという意味でタスクを小さく分割するのがよい(ここはもっと上手に振る舞うエージェントを作ることができるんじゃないかと期待している)
- Cline の場合は Plan モード、GitHub Copilot なら Chat を使って練るのは大事
- 練ったからと言って出力が思い通りになるかというとそれは難しいけど、初手で暴れ馬にコードを生成させまくるとあとが大変になる
進む
- 上記は基本的には注意すべきことなんだけど、いちばん強く感じた効果は「直さなきゃいけないコードが生まれる方がゼロからコードを生むよりも心理的には楽」ただし疲れる
- ドキュメントもめちゃくちゃ面倒なんだけど、書き始める億劫さだけは書かれてしまうのでスキップできる。あとは叩き直すだけ
- 自分で書かずにスピードを稼ぐフェーズが生まれる分、人間の意識は読むことと直すことに集中する格好になる。簡単なコードで中身を気にする必要がなく生成 ≒ 開発みたいなもの7は楽になるし生産性は高い
なんかきれいにまとめてる風だけど、実際に使ってる時の感想はけっこうヒドイ。
常にルールの不備をついてくるし、ルールを忘れるのはマジでこいつセンスないな、って思うんだけど明文化するとはこういうことか、とひたすら学びがある。いかに自分が暗黙知に頼っているかが露呈する。疲れる。
— wtnabe, yet another yak shaver (@wtnabe) March 9, 2025
少しtempature下げられないかなーと思う。書きすぎるきらいがある。もっと悩んでいい。
ツールの使い分けとフィット感
当初は terminal を操作してテストの結果を受け取ったりできる Cline すげーと思っていたんだけど、ディティールの改善に入っていけばいくほど、コードそのものとの距離が縮めば縮むほど、プロンプトで指示を出してコードを書かせるのが面倒になってくる。
こうなってくるとテストを動かすとか開発手順的な部分はともかく、人間がコードを触りながら考える部分は GitHub Copilot の方が向いているかもなぁと思っている。なんならファイルを watch して自動テストを回しっぱなしにする流派もあるわけで、その体制ができて以降は別に Cline でなくて Copilot だけで十分かもしれない。
また GitHub Copilot も以前からあった高級補完機能だけではなくなっていて、以下のように Cline に対応するような機能の分け方はされている。
役割 | 機能 | |
---|---|---|
設計 | Copilot Chat | Cline Plan mode |
実装 | Copilot Edits | Cline Act mode |
ただ、Cline は自身の担当するタスクについて開発プロセスとコード全般に対して考慮されているのに対して、Copilot は今のところあくまでコードを生成することがゴールかのような振る舞いをしやすい。名前も Copilot の方は Plan じゃなくて Chat であり、この Chat は完全に設計向きというよりは explain やいろんな役割を持っている。逆に Cline は明確に Plan mode であり、より良い設計を考えるために情報収集をもっと慎重に行ってくれるように感じた。これが AI エージェントとしての味付けの違いなのかな。あんまり詳しいことは分かってないけど。
※ あくまでツールを使って作ってみるのが主眼であって、製品の設計ポリシーみたいな情報収集は今回はしてません。
料金について
ちょっと特殊な構成で
- Cline
- Vertex AI Model Garden
- Claude 3.7 Sonnet
- Vertex AI Model Garden
にしてある。Anthropic に直接払っていない。もしかしたら割高かも。Copilot も preview の Claude 3.7 Sonnet を利用している。で、
Cline は 3週間で 26 USD ほどの消費で、いちばん最初にバーっとコードを書かせた時にいちばん使っている。小さく見直しなどを始めると利用しているコンピューティングリソースと得られる恩恵の割に出力は少ないので全然安く収まる。
まぁ以前はこれ全部自力で書いていたので 0円で済んでいたものにお金掛けてるのか、という話でもあるけど、やってみないと分からないし、出力させて以降の直しではそんなに消費しないし、でも成果物を増やしやすいし、仕事で使う分にはいろいろよさげな気配を感じた。
個人でもうまいことやれば GitHub Copilot Free + Claude 3.5 Sonnet とかでも Copilot Chat / Copilot Edits でもやれることはあるし、今後もできることはまだまだ増えるだろうなと思う。
課金するにしても Anthropic は基本的に従量課金だし、定額で ChatGPT Pro にバーンと払うような使い方よりは全然安いので、アリだと思う。
自分の作業について
今のところ自分の開発作業に AI を組み込むとしたら
- 「普段使っていないので使い方があやふやだが調べればなんとかなる、内容自体は難しくないもの」に対して使う
- 手間が掛かって面倒なだけの作業を代わりに網羅的にやってもらう
辺りに使うと効果はバツグンだと思う。ある程度以上できる人ならそこまで負荷を掛けずにできる幅を広げることができるし、作業時間とそれによるMPの低下時間を短縮しやすい。
もう一つは そんなに納得いかないコードでも直せばオッケーという状態まで持っていくと強制的に進む という効果も小さくないと思う。生成が早い分、読んで修正するサイクルがとても速くなって人間はより疲弊するが、成果の出る速度も速い。
逆に今回やったみたいな設計に検討の余地があるものに対しても、ルールで縛り上げてある程度我慢できる品質のコードを生成できるようになっているのであれば、動かしながら設計を検討する準備が早くできるという捉え方をすれば非常に効果的だ。開発そのものというよりは生成させたコードを使って設計する、という建て付けなら悪くない。
ただ総じてこれで自分のコーディング作業をなくせるかというと、そんなことはまったくないなと思う。これは自分が TDD を意識していることと根本的な理由はたぶん同じで、ゼロイチで作って放り投げておしまい、という仕事ばかりではないから、だと思う。触れれば壊れるレガシーコードの大海原のど真ん中に放り出されてサバイブしたらそらそうなる。
今後の開発「業務」について
効果の捉え方はまだまだ練っていく必要があるとは言え、現時点でまぁ及第点というか実用にはなると思う。そこで今後
- 開発業務そのもの
- 採用
- 育成
すべてにおいてAI を活用した開発が影響してくると感じている。たぶんだけど AI 抜きで考えるのはもう無理じゃないかな。少なくとも自分が関わるような領域だと。もっとコンピューティングに近い領域やライセンスに厳しい領域だとどうなるかはちょっと分からないけど。(一応 GitHub Copilot はライセンス問題に配慮した挙動はある)
個の開発者として感じる恩恵部分は上に書いたが、なんらかの歯車としてのソフトウェアエンジニアという位置付けとしては、AI の活用を前提とした開発になっていく流れについては、今のところ難しさしか感じない。
まず危惧することとしては、もろもろ速くなる分、疲れるのも速く、これが品質に悪い影響を及ぼす可能性も小さくないなと思う。製造に近い、人数で殴るタイプの開発に AI を導入するとスピードが上がって品質が下がる可能性は十分ある。8これは現時点でレビュー系の AI が生成系の AI ほど充実していないからでもある。
端的に言うとジュニアレベルの開発者に AI を渡し、爆速で成果物が生み出され続け、シニアがレビューに追われるという構図が容易に想像できて、これはもう本当に地獄しか生まないと思う。
だから
- AI にある程度の小さいタスクを指示できること
- AI の書いたコードをまず自分である程度レビューできること
辺りを最低ラインとして目指さないといけないかなと思うんだけど、そうなるとジュニアの人は今まで
- 指示を受ける
- 頑張って理解する
- 頑張って調べる
- 頑張って読む
- 頑張って書く
- 書いたものを読んで設計を練る
- なんとか動くようになったものをレビューしてもらう
で伸ばしていたスキルについて、まず指示を出して、読んで直して、をくり返しながら今までと同等以上のスキルを身につけなきゃいけなくなってくることになる。おかしい、順番が狂ってしまった。指示出しができるのはそもそもある程度以上の設計スキルがあることを意味する。あれ、どうやって開発を始めたらいいんだ?
まぁもしかしたら「指示を受ける」の部分が「指示プロンプトを受け取る」になるのかもしれないけど、だとするとあとはマジで磨きの部分しか残らなくなってしまうので、「はじまりの村」9から次の町へ行く難易度が上がってしまっている。
いや、もしかしたら「指示出しを上手にできるように」が目標になることで結果的に分解や設計が上手になる可能性はあるので、採用も育成も AI のおかげでスピードアップする可能性もなくはない。これは正直なんとも言えないし、もしかしたらこれまでの流行よりも手動テスト力の重要性が増す可能性もある。10
まぁもちろん、上に書いたような「よくないコード」というものに対しては何も見なかったことにして、コードの動作結果だけを見て、中の品質は気にしない(改修は受け付けない)というコードもあるだろうから、そういうところに積極的に使いましょう、はアリかもしれないけど。
良心的なところは AI を使って安く開発しますよ、みたいな商品も出てくるかもしれない。で、人間が磨いたあとのコードの価値が上がる、みたいな伝統工芸みたいな世界は…こない気がするなぁ。実際に自分で開発したことのある人は他の製品と同じように「良いものを長く使う」ことが”結果的に安上がり”という感覚を持てるとは思うけど、ただ買って使うだけだと物理の製品よりその辺は感じにくいだろうし…。あー JavaScript や LL のような Runtime が短命なプロジェクトにおいてはトータルの開発費(メンテナンス工数)を低く抑えることができるので、そういうところは積極的に取り入れていった方がいいのかなぁ。
防災系の連絡手段の登録になぜかPCで利用可能なメールが指定されていた(コストかなぁ)ので、Gmail の通知自体を切るのはさすがにためらわれる。 ↩
Writer で Document を足したいとか ↩
厳密なコードの移動になっているか分からないのでこう呼ぶ ↩
ただし、Gemini 2.0 Flash は token の限界を超える明確なエラーではないけど絶妙に内部で閾値があるのか、 content の統制がちゃんと取れてないのか、JSON decode 後なのに JSON が残るとか謎の挙動をすることがある。あと API call のタイミングによって変わる感じもある。LLM の挙動を従来の安定した API と同列に考えるのはやはりまだ早そうではある。 ↩
今回は GAS だし自分でコードを与えて動的に組む感じなのでセキュリティとかは考えてない ↩
自分はインラインコメントも禁止してコメント書きたくなったら関数を分けろってしたけど、どこまで有効に機能しているのかなんとも言えない ↩
それがどの程度の比率になるのかは領域や業務によって変わると思う ↩
そもそも人数で殴るタイプで品質を維持するのはかなり難しいと思うけど ↩
ドラクエとかのアレね ↩
結局ソフトウェアエンジにとって IaC も CI/CD も AI も総合力向上を要求してくる恐ろしいツールである。 ↩