世界を動かす技術を、日本語で。

Goはエージェントに適している

概要

  • エージェント の普及により、Go言語の利用が増加傾向
  • Goの 並行処理モデル がエージェント運用に最適
  • キャンセル機構 や標準ライブラリの充実が強み
  • PythonやNode.jsと比較した Goの優位性 と課題
  • 実運用や開発効率の観点から Go導入の検討ポイント

エージェント時代のGo活用のすすめ

  • 最近、 Hatchet のようなエージェント・データパイプライン基盤の需要が増加
  • バックエンドは Next.jsFastAPI、エージェント本体は Go で実装するハイブリッド構成が増加傾向
  • エージェントの定義: ループ実行型プロセス、処理ごとに次のステップを自律選択
  • ワークフロー(事前定義された実行経路)と対比し、エージェントは 柔軟な分岐や終了条件 を持つ
  • 実際の運用では、 長時間稼働高コストな実行人や他エージェントからの入力I/O待ち時間の多さ が特徴

Goがエージェントに適している理由

高い並行処理性能

  • goroutine の生成コストが低く、 数千単位の並列処理 が容易
  • 各goroutineは 2KBのメモリ で動作し、マルチコアを最大活用
  • CPU負荷の高い処理でも Node.jsやPythonより影響が少ない
  • Goでは メモリ共有による通信 ではなく、 チャネルによる通信 が推奨
    • これにより ミューテックス 不要のシンプルな設計が可能
  • エージェントのような 非同期応答メッセージ駆動型 設計に最適

集中管理型のキャンセル機構

  • Goの context パッケージにより、 実行中の処理を簡単にキャンセル 可能
    • サードパーティ製ライブラリもこの仕組みに準拠
  • Node.jsやPython ではキャンセル処理が難しく、リソースリークの温床

標準ライブラリの充実

  • net/httpio など、 Web I/O向けの高品質な標準ライブラリ が充実
  • goroutineベースの 直線的なプログラム設計 を推奨
  • Pythonでは asyncioマルチスレッド など複数の並行処理モデルが混在し、設計が複雑化

プロファイリングとデバッグのしやすさ

  • pprof などのツールで メモリリークgoroutineリーク を容易に特定
  • 長時間稼働するエージェントの 健全性維持 に有効

LLMによるGoコード生成のしやすさ

  • Goの シンプルな構文 と標準ライブラリにより、 LLMがGoコードを自動生成しやすい
  • フレームワーク依存が少なく、 テーブルテスト などのパターンも自動化しやすい

Goエージェントの課題と注意点

  • サードパーティライブラリの充実度 はPythonやTypeScriptに劣る
  • 機械学習用途 には不向き
  • 最高速 を求める場合は RustやC++ の方が有利
  • エラーハンドリング が煩雑と感じる場合も

まとめ・導入検討ポイント

  • 高い並行処理性能キャンセル容易性デバッグ性 から、エージェント用途にGoは有力
  • PythonやNode.jsと比較し、 シンプルな設計運用コスト削減 を実現
  • 機械学習やライブラリ依存度 が高い場合は他言語も検討
  • エージェント基盤の構築・運用 にGoを選択肢として積極的に検討

Hackerたちの意見

AIエンジニアがJavaScriptに手をつける前に、新しい宇宙を作り出すことになるよ。AI言語の多様性が失われたのは、GoogleがSwift用のTensorFlowを引っ込めたときだね。

JSがエージェントに特に向いてるのはなんで?

JavaScriptを避けるのはAIエンジニアだけじゃないよ。-過去ほぼ30年、たくさんのJSを書いてきた人から。

これが正しい道だね。JSは最初からひどい言語で、バックエンドに持ってくるのは間違いだった。TSがあっても、基盤となる言語がクソであることには変わりない。だから、多くの人と同じように、JSやTSには触れたくないから、Go、Rust、Python、Ruby、Elixir、F#のどれかを書くよ。

MLの世界で、もっといい並行処理モデルがあればいいのに。数ヶ月前にGoでMLをやってみたけど、ほぼ不可能だった。ライブラリのサポートが全然なくて、何かをするにはgRPCコールかラッパーが必要なんだ。Pythonには限界があるし、C++は全てを冗長にしがちだよね。

長時間実行される、高コストなプロセスで待機が多い場合、goroutineを実行しているプロセスを殺しちゃうと、全ての作業を失うのが難点だね。待ってる間に状態をデータベースにシリアライズする方がいいかも?でも、これだと複雑さが増すし、こういうチェックポイントベースの状態機械を書くのが簡単な言語は知らないな。

Temporalは長時間実行されるプロセスのチェックポイントに結構いい感じで、言語に依存しないんだよね。

それがgoroutineやスレッド、または長時間実行されるプロセスの連鎖の問題だね。タスクは原子チャンクに分けなきゃいけないし、状態は何らかの方法でシリアライズする必要がある。それによって、失敗を再試行したり、エラーを調べたり、結果を後で参照したり、全体を複数のノードに分散させることができる。私の見解では、これはOban(https://github.com/oban-bg/oban)がElixirでこの種の問題をモデル化している方法だから、そうあるべきだと思う。ちなみに、私はこのプロジェクトの著者でメンテナーです。Elixir特有だけど、この資料は非同期タスクの永続性の重要性を強調してるよ: https://oban.pro/articles/oban-starts-where-tasks-end

長時間実行される高コストなプロセスで、待機が多い場合の欠点は、ゴルーチンを終了させるとすべての作業を失ってしまうことです。これは言語に関係なく当てはまります。毎回、ゴルーチンで合理的な量の作業(ミリ秒から数秒程度)を行うようにしています。それ以上になると、ウェブサービスは本来のステートレスさを失ってしまいます。

OPです - この「チェックポイントベースの状態機械」は、Hatchet(https://hatchet.run/)やTemporal(https://temporal.io/)のような耐久性のある実行プリミティブを提供するプラットフォームがまさに提供しているものです。ちなみに、私はHatchetの創業者です。これらのプラットフォームは、同じワークフローの一部として実行された関数のイベント履歴を保存し、関数が中断されたときに自動的にそれを再生します。言語レベルでメモリの内容を同期させるのは、出力レベルで同期させるよりもずっとオーバーヘッドが大きいと思います。

実は今、golangでエージェントライブラリを作っていて、これがまさに私の考え方なんだ。包括的なログがあれば、エージェントの状態をどの位置でも再構築できるんだ。リプレイも可能になるしね。必要なのはタイムスタンプ(エンドポイント)と親の実行だけで、その後に子供や分岐した実行を作れる。コンテキストツリーを保持するマップとデータベースを使うことで、古いセッションを削除して、必要なときにデータベースから再構築できる(例えば、ユーザー入力が必要な非同期エージェントセッションなど)。エージェントやワークフロー、ツールの個々のオブジェクトを保持する必要もなく、マップの中でステートレスにして、必要に応じてIDを通じてポインタを参照できる。そうすれば、以前のアクションやステップ、「コンテキスト」を保持するステートフルなオブジェクトができる。エージェントやワークフローの整合性を保つために、出力エージェントやワークフローをハッシュ化できる(私のシステムではこれらはシリアライズ可能)。ただし、基本的なエージェントやツールしか実装していないし、ログや再構築、キャンセルのロジックはまだ実際にはできていない。

これにタスクキューを使う良い方法を考えていて、Postgresのテーブルに基本的なものを使うかもしれない。メリットは、エージェントのサブタスクがサーバー間で負荷分散できるし、プロセスが終了してもタスクが落ちないし、観測性も向上することだね。デメリットは確実に複雑さだ。エージェントのコードの複雑さを大幅に増やさないアーキテクチャを計画するのが難しいよ。

これらのポイントがどれだけ有効かは分からないな。エージェントシステムの遅延の多くはLLMへのコールに起因すると思う。記事からの引用: 「エージェントはスケールし始めると(つまり、実際のユーザーがいるとき)いくつかの共通の特徴を持つことが多い。長時間実行される — 秒から分、時には時間まで。各実行はコストが高い — LLMコールだけでなく、エージェントの性質上、人間のオペレーターが必要なものを置き換えることになる。開発環境、ブラウザのインフラ、大きなドキュメント処理 — これらは全てお金がかかる。実行サイクルのどこかでユーザー(または別のエージェント)からの入力が必要なことが多い。I/Oや人間を待っている時間が多い。」1番目は特定の言語を指しているわけじゃないし、他のポイントは実行速度やサーバーサイドの効率があまり関係ないことを示してる。人々はエージェントに質問して、エージェントが作業している間に他のことをするんだ。もしエージェントがPythonで書かれているから数秒長くかかるとしても、ほとんどの場合、誰も気にしないと思うよ。PythonはAI関連のライブラリやサポートが山ほどあるから、エージェントには向いてると思うな。> Pythonと対比してみて: ライブラリ開発者はasyncio、マルチスレッド、マルチプロセス、eventlet、gevent、その他のパターンを考えなきゃいけない…エージェントはそんなに難しくなくて、全てを最適化しなくても大体の使い方(そしてお金を払うユーザー)を得られるよ。それに、どんなワークフローを作ってもサポートが山ほどあるから、少なくとも自分が取り組んでいることの一部は誰かがすでに試している可能性が高いから、盲目的に進む必要はないんだ。

パフォーマンスの観点からはその通りだけど、Goでエージェントを構築する際には、同時実行性やバックログ、バックプレッシャーを管理するための非常に確立されたパターンがあって助かったよ。ほとんどのインタラクションは、応答に数秒かかるリモートサービスとのトランザクションを含むからね。(どの言語でもエージェントは効果的に書けると思うし、Javascriptが一番人気の選択肢だと思う。コード生成については、エージェントでもCLIツールでもサーバーでも、GoとLLMの組み合わせは特にいい化学反応があると思う。)

Goはまだまだ同時実行性の面で優れています。デプロイもずっと楽で、必要なのは静的バイナリだけで、すべてのpip依存関係を持つ特注のPythonランタイムをデプロイする必要がないからね。

エージェントはオーケストレーションレイヤーだから、Go(またはErlang、Node)にぴったりだよ。特に、今エージェントと呼ばれているものは2年も経ってないから、AI関連のライブラリの山は必要ない。真剣なIOを行うものは、必要なドメイン特化のツールインターフェースの背後に抽象化されるべきだよ。

Elixir + BEAMベースのエージェントフレームワークをいじってるところだよ。今のところ、BEAM + SQLiteの組み合わせがエージェントには最適だと思う。アプリケーションを再デプロイせずにエージェントを安全に入れ替えられるし、同時実行性もBEAMが設計されたスケールよりずっと低いし、ステートフルまたはエフェメラルなエージェントを作るのもすごく簡単なんだ。私の計画は、Python、Typescript、Rustでベースエージェントをセットアップして、ユーザーが好きなプログラミング言語でより複雑なエージェントを書けるようにすることだよ。

Extism[0]プロジェクトとElixir SDK[1]をチェックしてみて。これを使えば、コアサービスやルーティング、メッセージパッシングなどをElixirで書けて、BEAM/OTPのすべてを活用できるし、他の言語で書かれた「エージェント」を小さなWasmモジュールとして埋め込むことができるよ。これはプロセス内プラグインのように機能するんだ。[0]: https://github.com/extism/extism [1]: https://github.com/extism/elixir-sdk

記事の論理に従えば、Elixirはエージェントにより適しているね。理想的だと思う。

エージェントは、LLMの応答を待っている時間が90%以上を占めていて、他のサービス(HTTP APIやDBなど)でAPIコールを実行することもあるよね。私の経験では、言語ランタイムのパフォーマンスはあまり重要じゃない。エージェントのパフォーマンスやスケールに影響を与える言語機能があるとしたら、それは実際にはJSONのシリアライズとデシリアライズのパフォーマンスだと思う。

私の経験では、エージェントにおいて2番目にコストがかかる関数(LLMコールの次)は、非同期編集の差分処理やパッチ適用、マージによる競合解決だね。これらの競合解決操作は低レベルのライブラリを呼び出すこともあるけど、シリアライズなどに比べるとかなり高コストな最適化問題だよ。

そうそう、JSONとネイティブに連携できるTypeScriptみたいな言語を使った方がいいね。Goよりもはるかに強力な型システムを持っていると言えるし。

Goはこの手のことにはそんなに悪くないよ。でも、他の多くの言語より特に優れているとも思わない。正直、コンパイラがあって非同期処理をそこそこサポートしている言語なら、どれでも仕事はできると思う。もちろん、これには幅広い言語が含まれる。エージェントは多く(ほとんどと言う人もいる)プロンプトエンジニアリングを含むから、マルチライン文字列やテンプレート文字列、文字列操作が得意な言語だと助かるよね。非同期処理については、言語が非同期のことをできるといいけど、それだけで十分かな?エージェントシステムは本質的にネットワーク越しに他のシステムにアクセスするから、タスクが長時間続くこともある。数分、数時間、あるいは数日かかることもあるし、その間にいろいろ起こるよ。個人的には、あるシステムがその状態を長時間プロセスに保持するモデルは理想的じゃないと思う。もっと堅牢で長持ちするものが必要で、数日間どこかで動いている状態をあまり依存しない方がいいかも。関連する状態を言語から外部化して、この種のことに最適化されたミドルウェアを使うという議論もあるよ。そういう方向に進んでいるものをいくつか見たけど、まだあまり多くはないね。人々はまだ車輪を再発明するのに忙しくて、実際には再発明する必要がない車輪がたくさんあることに気づいていないみたい。非同期ジョブのスケジューリングや処理、ファンアウトなどに本当に優れたミドルウェアがたくさんあるから、最終的にはみんなそれが必要だと気づくと思う。

Goのひどくて限られた型システムは、すべてにおいて不適切だよ。Goの最悪なところは、実際にはその言語自体なんだ。言語を除けば、他のすべてがそれを救っている。

「Goのひどくて限られた型システム」がエージェント作成の妨げになるって、もうちょっと詳しく教えてくれない?本気の質問なんだけど、Goの型システムの制限で簡単にできないことや全くできないことに興味があるんだ。

同意するよ。複数の戻り値は組み合わせられないし、エラーは例外よりはマシだけど、やっぱり冗長だし、チャネルには危険が多いし、enumは悲しいよね。でもそれでも、この言語にはいいところもたくさんあるよ。インターフェースは思ったよりもずっとうまく機能するし、パッケージの組み合わせもすごくいい感じ(今Rustを学んでるけど、ファイル構造がずっと複雑だし)。それに、言語がシンプルだからこそ、たくさんのリンターやコード生成ツールが書かれてるのもいいところ。全体的に見て、Goのコードの長期的なメンテナンスコストについてはあまり心配してないよ。特にPythonやJSのコードと比べるとね。

Goにコンパイルされる、よくメンテナンスされたLISP/Schemeの方言があってもいいな。

何年もGoでプログラミングしてきたけど、同意するよ。ただ、エコシステム全体がそれほど救済してくれるかはあまり自信がないな。一方で、LLMの人たちが使ってるプログラミング言語は主にPythonとJavaScriptみたいだね。だから、彼らは本当に現代の言語に移行すべきだと思うけど、GoはLLMプロジェクトでよく見られる、DockerfileすらないPythonやJavaScriptのインポートの混乱よりはまだマシだと思うよ。

この種のワークロードにはGoの利点は少ないよ。ほとんどの時間、I/O待ちになっちゃうし。言語自体の制約にも苦しむし、現代の言語で無料で得られる多くの型システムの機能は、Goでは回避策が必要なんだ。TypeScriptはあらゆるAIにとって素晴らしい接着剤言語だと思う。Pythonは、TSの後に、ベンダーからの広範なライブラリサポートがあるし。個人的には、型システムがもっと表現力豊かで成熟しているから、Pythonよりも好みだよ。でもPythonも急速に改善してるね。 > 「Node.jsやPythonでの長時間実行される作業のキャンセルは、いくつかの理由で非常に難しいことが判明しました。」この主張には証拠が不足している。ほとんどのツールはキャンセルをサポートしていて、ほとんどがPythonかJSだよ。