InternLM/lagent を採用する前に読む、AgentMessage とメモリ設計の実際
A lightweight framework for building LLM-based agents
ひと目でわかる
- これは何?
- lagent は PyTorch の層の比喩をそのままエージェント構築に持ち込んだ軽量フレームワークである。中心にあるのは AgentMessage による通信と、__call__ で自動的に積まれるメモリという二つの仕組みで、その設計を理解しないまま使うと、会話状態がどこで壊れるかが見えなくなる。
- 誰に向いている?
- lagent は、OpenAI 互換の chat 形式に閉じずに、メモリとアグリゲータとパーサを自分の手で差し替えたい開発者に向く。逆に、完成品のエージェント実行環境や長期の安定 API を求める用途には向かない。
- 商用利用できる?
- できます。Apache-2.0 は寛容なライセンスで、著作権表示とライセンス表示を残せば、使用・改変・販売が可能です。
- 今もメンテナンスされている?
- されています。最後のコミットは 2 日前です。
- 何の言語で書かれている?
- 主に Python です(GitHub の言語統計による)。
回答はプロジェクトの GitHub データ(最終同期:2026年9月15日)と当サイトの分析に基づくもので、法的助言ではありません。
オープンソース詳細解説
lagent が解こうとしている問題は、会話状態の置き場所である
多くの LLM アプリは、会話履歴をリストに積んで毎回 API に投げ直す。この方式は単純だが、履歴の持ち方、システム指示の差し込み方、モデル出力の解析をアプリ側で個別に書くことになる。lagent はこの三つをフレームワークの型として固定する。通信の単位は AgentMessage で、sender、content、formatted、extra_info、type、receiver、stream_state といったフィールドを持つ。README の例では、user の AgentMessage を Agent に渡すと、content='急'、sender='Agent'、stream_state=<AgentStatusCode.END: 0> という AgentMessage が返る。返り値が文字列ではなくメッセージオブジェクトである点が重要で、解析結果や送受信者を同じ構造に載せられる。対象読者は、モデルの呼び出しそのものより、履歴と出力形式の管理に手間を感じている Python 開発者である。
__call__ がメモリを書き換える、という設計上の選択
lagent の README は、入力と出力の両方が各 forward で Agent のメモリに追加されると説明している。追加が行われるのは forward ではなく __call__ である。疑似コードとして、pre_hooks の適用、add_memory、forward、add_memory、post_hooks という順序が示されている。つまり forward を自分で実装しても、履歴の追加は呼び出し側の __call__ が担う。この分離は、forward を純粋な変換として書きたい場合には都合がよい。一方で、forward を直接呼ぶとメモリに何も残らないという落とし穴でもある。メモリの確認方法は二通り用意されている。agent.memory.get_memory() は AgentMessage のリストを返し、agent.state_dict() は辞書のリストとしてダンプする。後者は永続化やシリアライズを想定した形に見える。セッションは session_id で分けられ、既定値は 0、agent.reset() でそのセッションのメモリを消す。
DefaultAggregator が AgentMessage を OpenAI 形式に変換する
AgentMessage はそのままではモデルに渡せない。変換を担うのが aggregator で、既定では DefaultAggregator が使われる。README に示された forward の実装では、self.aggregator.aggregate(self.memory.get(session_id), self.name, self.output_format, self.template) を呼び、得た formatted_messages を self.llm.chat に渡している。aggregate の引数にメモリ、エージェント名、出力パーサ、テンプレートが並ぶことから、変換は履歴の読み出しとプロンプト組み立てを兼ねていると読める。ここが差し替え可能である点が lagent の性格を決めている。FewshotAggregator の例では DefaultAggregator を継承し、aggregate_system_intruction の後に few_shot のリストを展開し、sender がエージェント名と一致するメッセージを assistant、それ以外を user として積む。連続する user メッセージは直前の content に連結される。この連結規則は、複数のツール結果を一つの user 発話にまとめたい場合に効くが、sender の命名を間違えると役割が反転する。
output_format と ToolParser が解析結果を formatted に逃がす
モデル出力から構造化データを取り出す処理は、content を汚さずに formatted へ入れる。README の forward では、self.output_format が設定されている場合、parse_response(llm_response) の結果を formatted に持たせた AgentMessage を返す。解析に失敗した場合の扱いについては、示された抜粋からは読み取れない。ToolParser を使う例では、system_prompt として「段階的に分析し Python コードを書いて問題を解く」という指示を与え、パーサを組み合わせる流れが示されている。この分離により、生のテキストは content に、ツール呼び出しの解釈は formatted に残り、後段の処理がどちらを見るかを選べる。ただしパーサの出力形式はモデルの出力揺れに直接さらされる。テンプレートとパーサをセットで管理しないと、モデルを差し替えた時点で解析が崩れる。README はこの点の失敗例を提示していないため、運用側で検証する必要がある。
導入は pip install -e . から、設定はコードに埋め込む
インストールはソースから行う。git clone https://github.com/InternLM/lagent.git の後、cd lagent して pip install -e . を実行する。PyPI に lagent が公開されていることはバッジで示されているが、README の手順は editable インストールを前提にしている。モデルの指定はコード内で行う。例では VllmModel に対し path='Qwen/Qwen2-7B-Instruct'、meta_template=INTERNLM2_META、tp=1、top_k=1、temperature=1.0、stop_words=['<|im_end|>']、max_new_tokens=1024 を渡している。設定ファイルや環境変数による外部化は README には現れない。エージェント側は Agent(llm, system_prompt) で作り、system_prompt は文字列で渡す。カスタムアグリゲータを使う場合は Agent(llm, aggregator=FewshotAggregator([...])) のようにキーワードで差し込む。メモリの消去は agent.reset()、状態の取り出しは agent.state_dict() である。
メモリが __call__ 依存であることが招く失敗
最も見落としやすい制約は、履歴の追加が __call__ に集約されている点である。forward をテストのために直接呼ぶと、メモリは更新されない。逆に __call__ を通す限り、入力も出力も自動で積まれるため、アプリ側で履歴を二重に管理すると重複する。session_id の既定値が 0 であることも、単一プロセスで複数ユーザーを扱う場合には注意が要る。既定のままなら全員が同じセッションに乗る。README は reset で session_id=0 を消すと説明しており、複数セッションの扱いについては記述が薄い。また FewshotAggregator の例が示すように、sender の値が役割判定に直結する。エージェント名と sender がずれたメッセージは user として扱われる。ストリーミングの途中状態は stream_state に AgentStatusCode として入るが、この列挙の全容は抜粋からは確認できない。
LangChain との違いは抽象の置き場所にある
比較対象として LangChain を挙げるなら、差は抽象をどこに置くかである。LangChain はチェーン、ツール、メモリ、リトリーバといった部品を多数用意し、それらを組み合わせてアプリを組む。lagent が固定するのは AgentMessage という通信単位と、aggregator、output_format、memory という三つの差し替え点に限られる。README 自身が PyTorch の層の比喩に触れ、層を作りメッセージ受け渡しを定義することに集中すればよいと述べている。つまり lagent は部品の数で選ぶフレームワークではなく、変換と解析の境界を自分で書きたい人向けである。逆に、既製のツール群や統合をそのまま使いたい場合、lagent では aggregator やパーサを自作する前提になる。どちらが優れているという話ではなく、抽象の総量を減らして制御を残すか、抽象を増やして記述を短くするかの選択である。
Apache-2.0 とリリース状況から見る保守コスト
ライセンスは Apache-2.0 で、リポジトリの LICENSE へのリンクが README にある。Apache-2.0 は特許許諾条項を含む寛容なライセンスとして知られるが、本記事は法的助言ではない。自組織のポリシーに照らした確認は別途必要である。リリース履歴を見ると、agentrl_rc0 が 2026-05-19、v0.5.0rc3 が 2025-03-04、v0.5.0rc2 が 2024-11-29 で、いずれも rc 表記である。安定版の番号が README やリリース一覧からは確認できない。既定ブランチは main で、アーカイブはされていない。この状況は、API が固定されていない前提でコードを書く必要があることを意味する。aggregator やパーサを自作している場合、内部の呼び出し規約が変わると影響は自分のコードに及ぶ。editable インストールで使うなら、更新前に agent.state_dict() の出力形式と aggregate のシグネチャを確認するのが現実的な手順になる。
編集部の結論
lagent は、OpenAI 互換の chat 形式に閉じずに、メモリとアグリゲータとパーサを自分の手で差し替えたい開発者に向く。逆に、完成品のエージェント実行環境や長期の安定 API を求める用途には向かない。採用前に確認すべきは、pip install -e . で入る lagent のバージョンと、agent.state_dict()['memory'] に期待する形が自分のアプリの永続化形式と一致するかである。特に __call__ が forward の前後でメモリに書き込む点を前提に、セッション分離を session_id でどう切るかを先に決めておかないと、後からメモリ構造を変えるコストが大きくなる。
コミュニティノート