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

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

Hacker Newsで議論の続きを見る