- 近年、AI エージェントの導入が増えるにつれ、Go言語ベースのハイブリッドスタック活用も増加する傾向にある
- エージェントは 長い実行時間、高いコスト、そして 入出力待ち が多いという特性を持つ
- Goは 軽量ゴルーチン、集中管理されたキャンセル機構、チャネルベースのメッセージング など、高性能な並行処理モデルを提供する
- 標準ライブラリが非常に充実しており、プロファイリングツール
pprof によって メモリおよびスレッドリーク の追跡が容易である
- ただし、Goには 機械学習エコシステムの不足、突出した最高性能ではないこと、他言語と比べて少ないサードパーティ支援 といった限界もある
エージェントとは何か
- エージェントは反復ループの中で実行され、次の実行ステップを自ら決定できるプロセスを意味する
- ワークフローのような事前定義された経路ではなく、条件(例: 「テスト通過」)や最大反復回数などによって終了可否を判断する
- 実運用では、エージェントは 数秒から数時間にわたって長時間実行 され、LLM呼び出しやブラウザ操作などにより コストが高い
- ユーザーの入力(または別のエージェントからの入力)を処理する必要があるため、入出力(I/O)待ち時間が多い
Go言語がエージェントに適している理由
高性能な並行処理
- Goの ゴルーチン は、2KBのメモリだけで数千〜数万個の軽量スレッドを同時に実行できる
- 各ゴルーチンは マルチコア を活用して並列処理を行い、I/Oや待機状態のエージェントも無理なく運用できる
- チャネル(Channel)ベースの通信 により、メモリ共有ではなくメッセージ受け渡しで同期を実現する(
Mutex の使用を最小化)
- エージェントが非同期にメッセージをやり取りしながら状態を管理するのに適している
集中管理されたキャンセル機構
- Goの
context.Context を活用すると、ほとんどのライブラリやAPIがキャンセルシグナルをサポートしているため、実行中断 が非常に容易である
- Node.jsやPythonは複数のキャンセルパターンが混在している一方、Goは一貫した方法で安全にキャンセルとリソース回収を行える
豊富な標準ライブラリ
- Goは 標準ライブラリ が非常に充実しており、HTTP/ウェブ、ファイル、ネットワークI/Oなど、ほぼあらゆる領域をカバーしている
- すべてのI/Oはゴルーチン内でのブロッキング動作を前提とするため、ビジネスロジックを直線的(ストレートライン)に記述できる
- Pythonは
asyncio、スレッディング、プロセスなど多様な並行処理パターンが混在していて複雑である
プロファイリングと診断ツール
- Goの
pprof などの組み込みツールにより、メモリリーク や ゴルーチン(スレッド)リーク をリアルタイムで追跡できる
- 長時間・同時実行されるエージェントで発生しうるリーク問題の診断に強みがある
LLMによるコーディング支援に優れる
- Goは 単純な文法 と豊富な標準ライブラリのおかげで、LLMが Goらしいスタイルのコード をうまく書ける
- フレームワーク依存性が低く、LLMがバージョンやパターンを気にする必要が少ない
Goの限界点
- サードパーティライブラリ とエコシステムは、PythonやTypeScriptと比べて不足している
- 機械学習を直接実装 する用途には向いていない(性能と支援に限界がある)
- 最高性能 が必要な場合は、RustやC++のほうが優れている
- エラーハンドリング に寛容な開発者にとっては、やや煩わしく感じられることがある
2件のコメント
JavaよりはGo、GoよりはRust :)
Hacker Newsの意見
ほとんどのエージェントシステムで最大の遅延要因は結局LLM呼び出しだと強調している。記事で挙げられている利点は特定の言語に有利なものではなく、多くは長時間の待機、高価なリソース使用、ユーザーまたは他のエージェントからの入力、そしてI/O待ち時間が大きいことに由来する。こうした特性を考えると、サーバー実行速度や効率性よりも、むしろPythonのような言語が持つ膨大なAIライブラリとサポートのほうが重要な利点だと主張している。Pythonでは
asyncioやマルチスレッドライブラリを考慮しなければならないという指摘もあるが、実際のエージェント開発はそれほど難しくなく、すでに誰かが関連ワークフローを構築した経験があるため、簡単に始められると考えているGoでエージェントを構築する際には、並行性とバックプレッシャー管理のパターンがよく確立されている点が大きな利点だと経験している。エージェントにはたいてい低速な外部サービスとのトランザクションが含まれ、この種の作業ではGoの並行処理パターンが非常に有用だ。もちろん言語はそれほど重要ではなく、JavaScriptが最も多く使われているように見える。ただし、コード生成であればGoとLLMの組み合わせには良いシナジーがあると感じる
Goは優れた並行処理が可能で、デプロイも容易という点でPythonと差別化される。Goは静的バイナリを配布するだけでよいため、Pythonの環境や依存関係の問題から自由でいられる
エージェントはオーケストレーション層の役割を果たすので、Go、Erlang、Nodeが特に適していると思う。大量のAI関連ライブラリが必須というわけではなく、I/Oが多い作業はドメインごとのツールインターフェースの背後に抽象化し、必要な言語でサブシステムを作ればよい点を強調している
Goはこの種のワークロードでは大きな利点があるわけではなく、ほとんどの時間をI/O待ちに費やす。Goの型システムは制限が多く、最近の言語なら標準で備えている多くの機能をGoでは回避策で補う必要がある。TypeScriptはAI用途に優れたグルー言語であり、Pythonと並んでライブラリ支援も非常に良い。私がPythonよりTypeScriptを好む理由は、型システムがはるかに強力で成熟しているからだ。Pythonも急速に改善している。Node.jsやPythonでは長時間実行タスクの中断が非常に難しいという主張には、はっきりした根拠がないと思う。ほとんどのツールがすでにこの機能をサポートしており、主要言語はPythonとJSだ
ElixirとBEAMベースのエージェントフレームワークを試してみたが、現時点ではBEAMとSQLiteの組み合わせがエージェントに最も理想的だと思う。アプリケーションを再デプロイせずにエージェントを安全に差し替えられ、BEAMの並行性にはこの作業をこなす十分な余裕がある。状態ベースや一時的なエージェントの実装も非常に容易だ。今後はPython、Typescript、Rustでベースエージェントを構築し、各言語の好みに応じて複雑なエージェント開発ができるようMCPサーバーも作る予定だ
ExtismプロジェクトとElixir SDKを勧める。この組み合わせを使えば、Elixirでコアサービス、ルーティング、メッセージパッシングを作り、BEAM/OTPの利点を活かせるうえ、他言語で書かれた小さく軽量なWasmモジュール形式のエージェントもプラグインのように埋め込める
Extism
Elixir SDK
BEAMの組み込みデータストアであるmnesiaではなくSQLiteを選んだ理由があるのか気になる
mnesia docs
エージェントの時間の大半はLLM応答待ちと外部サービス(API、DB)呼び出しに使われる。言語ランタイムの性能が実際に与える影響はほとんどない。エージェントの性能とスケーラビリティで本当に重要な言語機能があるとすれば、それはJSONのシリアライズとデシリアライズ性能だろう
だから、JSONをネイティブに扱えるTypeScriptのような言語を使うほうがよいと思う。TypeScriptの型システムはGoよりもずっと強力だ
経験上、LLM呼び出し以外でエージェントにおいて最もコストが高いのは、非同期編集(merge、diff、patch)の競合解決だ。この作業も低レベルライブラリに委譲できるが、シリアライズと同じくらい最適化が難しい問題だ
このampcode.comのエージェント構築ガイドを思い出した。Pythonは動的言語という特性のおかげで、デコレータでメソッドをそのままツール呼び出しに変換したり、ツール関数の反復でリストを作ったり、JSONスキーマへ素早く変換したりといった使い方が自然だ。一方、複数の外部トリガー(例: ユーザー入力、Gmailメール、Slackメッセージなど)によって新しいエージェント実行を引き起こす構造は、Goのチャネルと
switch forループを使うほうがはるかに直感的だった。Pythonでは複数のキューとスレッドを別々に作る必要があり複雑だ記事の論理に従うなら、Elixirはエージェントに理想的だ
Goの制限が多く物足りない型システムは、ほぼすべてのアプリケーションに不向きだと思う。実際、Goの最大の欠点は言語そのものだと評価している。むしろ言語以外の部分が、Goを受け入れられる要因になっている
数年間Goでプログラミングしてきたが、型システムに問題が多いという点には同意する。LLM分野の人たちはほとんどPythonかJavaScriptしか使っていないようだ。皆がもっと新しい言語に移るべきだと思うが、GoはPython/JavaScriptの散らかったインポートやパッケージ問題に比べれば、まだましな選択肢かもしれない
Goの型システムの限界がエージェント開発でどう障害になるのか、具体的に聞いてみたい
実際のところ、Goに静的型システムが入ったのは、性能要件を達成する方法を見つけられなかったからだ。実質的には動的型付け言語のように使われていると見るべきで、言語設計の目的を誤解した結果だ。動的型付け言語が全般的に不適切だと主張することはできるかもしれないが、実際にはPython、Erlang、Elixirなどの動的型付け言語が活発に使われていることから、むしろ動的型付けのほうが問題領域に適している
複数戻り値は合成しづらく、エラー処理は例外よりは良いものの非常に冗長で、チャネルはミスしやすく、
enum型は期待外れだと感じる。それでもインターフェースは意外とうまく機能し、パッケージングシステムもかなり滑らかだ。Rustを学ぶ中で、file structureがGoよりはるかに複雑だと分かった。言語が単純だからこそ、さまざまなリンターやコード生成ツールも作りやすい。Goコードの長期保守については、Python/JSに比べて心配が少ないGoにコンパイルされるLISP/Scheme方言がきちんと保守されていたら本当に良いのにと思う
待ち時間が長く実行コストも高いプロセスでは、プロセスが落ちるとすべての作業を失う点が欠点だ。待機中に状態をデータベースへシリアライズするほうが安全かもしれないが、これを簡単に実装できる言語はないように思う。チェックポイントベースの状態マシンを書くのは簡単ではない
チェックポイントベースの状態マシンはHatchet(hatchet.run)やTemporal(temporal.io)のようなプラットフォームが提供する中核機能だと説明している。ワークフロー内の関数実行履歴を保存し、割り込みが発生したときは自動的に履歴を再生する。メモリスナップショットよりも、出力単位の進行履歴のほうがずっと効率的だと主張している。(Hatchet創業者)
goroutine、スレッド、長時間実行チェーンは結局アトミックな作業単位に分割し、状態のシリアライズが必須になる。障害復旧、エラー追跡、結果の再参照、マルチノード分散などの要件を満たせる。Oban(github.com/oban-bg/oban)フレームワーク(Elixir)がこの方式であり、非同期ジョブ永続化の重要性を強調したOban記事も勧めている。(Oban作者)
golangベースのエージェントライブラリを開発中だが、十分なロギングさえあればいつでもエージェント状態を復元できるのではないかと感じた。タイムスタンプと親実行(run)さえ分かれば、子/分岐実行ツリーを積み上げられる。マップとDBを併用してセッションを管理し、必要に応じて再構築できる。個々のオブジェクトを保持せず、statelessなオブジェクトはマップからidで引いて使い、以前のアクション、ステップ、コンテキストは状態オブジェクトに置く構造だ。エージェント/ワークフローの一貫性も結果をハッシュで管理して解決する。まだ基本的なエージェント/ツールしか実装しておらず、ロギング、復元、キャンセルのロジックは未実装のままだ
Temporalは長期プロセスのチェックポイントにかなり有用で、言語にも中立的だ
私もジョブキューを検討していて、Postgresに簡易的なキューを作るべきか考えている。利点はサーバー間でのワークロード分散、プロセス終了後のタスク消失防止、より良い可視性の確保だ。その一方でコードの複雑さが大きく増す可能性があり、アーキテクチャを簡潔に設計するのが難しいと感じる
AIエンジニアはJavaScriptの使用を極端に嫌がる。TensorFlow for Swiftの終了が、AI言語の多様性の終わりだった
JavaScript忌避はAIエンジニアだけの話ではないと思う。30年以上JSを書いてきた人間としても同感だ
JSは言語として非常に出来が悪く、バックエンドに持ち込んだのは失敗だったと思う。TypeScriptも結局、その基盤にあるJSの問題を解決できていない。JSやTSを使うのは避け、Go、Rust、Python、Ruby、Elixir、F#など他の選択肢を好む
JSがエージェントに特別向いている理由が気になる
ML分野では、より良い並行性モデルが必要だと感じる。GoでMLを試したことがあるが、ライブラリ支援の不足と、外部gRPC呼び出しやラッパーへの依存のため、実質的に不可能だった。Pythonには限界があり、C++は冗長で生産性を下げる要因が多い