dspy.RLM is out
朝,寒すぎる自宅の床に降り立つのが嫌でベッドでうだうだしながらTwitterを見ていたら以下のツイートを発見.
一体RLMとは何なのかを確認してみた.
RLM is 何
現状,公式のAPI ReferenceにはRLMの項が無さそう……?
ということで色々調べてみたところ,そもそも"RLM"とは"Recursive Language Models"の略で,同名の論文で提案されたモデルのようです.
arxivで論文が発表されたのは2025年年末のようですが,2025年10月には著者の1人のブログでやはり同名の記事が投稿されているようです.
(ブログからarxivへのリンクもされています)
このように,提案されて間もないモデルが使えるようになるのはDSPyの強みと言えそうです.
肝心のRLMの中身ですが,論文やブログを(非常に)流し読みすると,ポイントは"長いコンテキストをうまく処理する"ことのようです.
論文のasbtractには
a general inference strategy that treats long prompts as part of an external environment and allows the LLM to programmatically examine, decompose, and recursively call itself over snippets of the prompt
―― https://arxiv.org/html/2512.24601v1 より引用
とあります.
この"treats long prompts as part of an external environment"というのが最初は意味がわからなかったのですが,上記論文のFigure.2を見ると,司令塔のような役割を担うRLMが,ロングコンテキストを適切に分割して子のRLMに渡す,という構成のことを指しているらしいということがわかりました.
サブエージェントを使う考え方に近そうに感じました.
dspy.RLMを使ってみる
概念の基礎(だけ)わかったところで,dspy.RLMを使って挙動を確認してみたいと思います.
今回はロングコンテキストであるほうが動きが分かりやすそうなので,自分が行きたい場所や行った場所を記録するために使っている自作のウェブアプリのデータをエクスポートして,「私がいつか行きたいor実際今まで行った場所一覧」をjsonにしたものを用意しました.
OpenAIのTokenizerで確認したところ23万強のトークンになります.
本当はもっと巨大なコンテキストが用意出来たほうが面白そうですが,今回は動きの確認ということでこのデータを使います.
比較用にまずはdspy.Predict
まずはRLMを使わずに普通にPredictする例です.
from dotenv import load_dotenv
import dspy
from langfuse import get_client
from openinference.instrumentation.dspy import DSPyInstrumentor
import os
load_dotenv()
langfuse = get_client()
DSPyInstrumentor().instrument()
OPENAI_API_KEY = os.environ['OPENAI_API_KEY']
class MyTripAdvisor(dspy.Signature):
"""Answer the question based on 'my favorite spot information'"""
question: str = dspy.InputField(desc="question")
my_favorite_spot_information: str = dspy.InputField(desc="my favorite spot information")
answer: str = dspy.OutputField(desc="Answer to the question in Japanese")
def main():
with open("data/spots.json", "r", encoding="utf-8") as f:
spots_json_str = f.read()
print(len(spots_json_str))
lm = dspy.LM("openai/gpt-5.2-2025-12-11", api_key=OPENAI_API_KEY)
dspy.configure(lm=lm)
simple = dspy.Predict(MyTripAdvisor)
result = simple(question="シチリア島を観光するならどこに行くのがオススメ?", my_favorite_spot_information=spots_json_str)
print(result)
if __name__ == "__main__":
main()
この場合は,巨大な「my_favorite_spot_information」が載った1回のLLMのコールが行われることが確認できます.
dspy.RLM
コード
次に,RLMを使ってみます.
なお,dspy.ProgramOfThought同様,Denoがインストールされている必要があるようです.
from dotenv import load_dotenv
import dspy
from langfuse import get_client
from openinference.instrumentation.dspy import DSPyInstrumentor
import os
load_dotenv()
langfuse = get_client()
DSPyInstrumentor().instrument()
OPENAI_API_KEY = os.environ['OPENAI_API_KEY']
class MyTripAdvisor(dspy.Signature):
"""Answer the question based on 'my favorite spot information'"""
question: str = dspy.InputField(desc="question")
my_favorite_spot_information: str = dspy.InputField(desc="my favorite spot information")
answer: str = dspy.OutputField(desc="Answer to the question in Japanese")
def main():
with open("data/spots.json", "r", encoding="utf-8") as f:
spots_json_str = f.read()
print(len(spots_json_str))
lm = dspy.LM("openai/gpt-5.2-2025-12-11", api_key=OPENAI_API_KEY)
dspy.configure(lm=lm)
# simple = dspy.Predict(MyTripAdvisor)
# result = simple(question="シチリア島を観光するならどこに行くのがオススメ?", my_favorite_spot_information=spots_json_str)
# print(result)
rlm = dspy.RLM(MyTripAdvisor)
result = rlm(question="シチリア島を観光するならどこに行くのがオススメ?", my_favorite_spot_information=spots_json_str)
print(result)
if __name__ == "__main__":
main()
トレース
LangFuseでトレースを見ると以下のようになっていました.

繰り返しLLMが呼ばれていることがわかります.
まず1回目の呼び出しでは,質問と行きたい場所データの冒頭部分がinputとして渡されます.

その出力としては,以下のようになっており,どのようにタスクを進めればよいか推論し,必要なcodeを生成しています.

上記のcodeはうまく動かないようで,次の呼び出しでは,エラー原因を推測しつつcodeを改善しているようです.

その後も,取得した情報をもとにreasoningを続けて,回答の方向性を定めて行っています.

最終的に,長文のコンテキストを繰り返し,段階的にLLMを呼び出し回答を導いていっていることがわかります.

今回のケースinputのデータがjson形式であることを見抜き,効率的にデータを分解・調査してタスクを進めていっていることがわかります.
一方でArxivの論文のFigure 2を見ると,データがjson形式でない場合は,例えば「Chapter 2」という文字で文字列をsplitすることで,"Chapter 2"より前(つまりChapter 1)と"Chapter 2"より後を分けて子のRLMに与えることで,長文を分割してタスクを進める,と言ったこともできることが期待されているようです.
今後は今回のサンプルのデータをjson形式ではなく例えばmarkdown形式で列挙するように変えたらどうなるか?など,試してみたいと思います.
コスト
なお,コストについてですが,最初はLLMを多数回呼び出すならコストは増えるのではないか?と思っていましたが,実際には
- dspy.Predict:0.51ドル
- dspy.RLM:0.08ドル
と,RLMのほうが安くなっていました.
これは前述の通り,json形式であることを見抜き,関連しそうな検索ワードで抽出した後に子のRLMを呼ぶ形となっているため,無駄に大量のコンテキストをモデルに与えることを避けられたため,と予想しています.
(なので,JSON形式でなくマークダウンだったらどうなったか?はわかりませんが,もう少しコストは増えると予想しています)
Latency
一方,Latencyは
- dspy.Predict:19.61s
- dspy.RLM:49.88s
と,RLMのほうが長くなっていました.
これは,RLMのほうがモデルを呼び出す回数が増えるがために,ある程度避けられないのでは?と予想しています.
DSPyにはevaluationとoptimizationの機能も充実しているので,今後はPredictとRLMをevaluateして結果を比較したり,RLMをoptimizeしたらどうなるかなど,引き続き試してみたいと思います.
(が,評価や最適化に使えそうな良いデータセットはあるだろうか……!)

