@mizchi/rlm
v0.1.1
Published
RLM (Recursive Language Models) TypeScript prototype
Readme
rlm
RLM (Recursive Language Models) の TypeScript 仮実装です。
参考論文
- https://arxiv.org/abs/2512.24601
利用者向けガイド:
- index:
docs/LIBRARY_GUIDE.md - 日本語:
docs/LIBRARY_GUIDE.ja.md - English:
docs/LIBRARY_GUIDE.en.md - semantics:
docs/SEMANTICS.ja.md
npm (JavaScript)
ライブラリ利用:
import { rlm } from '@mizchi/rlm';CLI 利用:
npx @mizchi/rlm --help npx @mizchi/rlm eval --provider mock --cases eval/cases.sample.jsonl --profile hybridリリース:
pnpm run release:npmrelease:npmはprepackでtsdownビルドを実行してからnpm publishします。
目的
- prompt 本文を LLM 履歴に入れず、REPL 環境に保持する
- LLM は JSON DSL を 1 ステップずつ返し、
env.finalで終了する - 再帰 (
subRLM)、Budget、Trace、評価パイプラインまでを動作させる
現在の設計
runRLMで Root ループを実行DSLReplがdoc_parse/doc_select_section/doc_table_sum/doc_select_rows/doc_project_columnsとslice/find/chunk/sub_map/reduce_join/finalizeを実行- 外部注入シンボルを
call_symbolで DSL から呼び出し可能 LLMProvider抽象 +OpenAIProvider/MockLLMProvider- 文書I/Oは
DocStore抽象で差し替え可能(InMemoryDocStore/MCPDocStore) response_format (json_schema)を利用し、DSL 出力を強制- 不正 DSL は
coerceとエラーメタで回復 - 早期終了ヒューリスティック(評価時有効)
追加した実運用向け対策
- DSL coercion(軽微な非準拠 JSON を補正)
sum_csv_column/pick_wordなど検証可能 op の追加StructuredDocument層(Markdown/CSV/Text)を導入し、文書を構造化して再利用enableHeuristicPostprocess(TOKEN 抽出 / CSV 合計 / 単語抽出)enableEarlyStopHeuristic(不要ステップ削減)- OpenAI 呼び出しタイムアウト
検証結果(OpenAI, gpt-4.1-mini)
日付: 2026-02-15
最終設定(早期終了あり)での直近実行:
- baseline:
2/3(66.7%), calls=3 - rlm:
3/3(100.0%), calls=6 - delta:
+33.3pt
保存レポート:
eval/report.openai.earlystop.json
このアプローチは正しいか
現段階では 方向性は妥当。
- 妥当な点:
- RLM の中核(prompt 本文を履歴外に置く)を維持
- DSL + Budget + Trace で制御可能
- 構造化タスク(抽出・検索・集計)に強い
- 注意点:
- 現在は Pure RLM ではなく
RLM + task-specific ops + heuristicsのハイブリッド
- 現在は Pure RLM ではなく
- 一般生成タスクへの直接一般化はまだ弱い
構造化文書IR(追加)
doc_parse:env.promptをStructuredDocument(markdown/csv/text)として scratch に載せる
doc_select_section:- Markdown セクションをタイトルで抽出
doc_table_sum:- CSV の列(index または header 名)を合計
doc_select_rows:- CSV の条件一致行だけを絞り込む(列名/列index対応、
eq/contains/gt/gte/lt/lte)
- CSV の条件一致行だけを絞り込む(列名/列index対応、
doc_project_columns:- CSV から指定列だけを射影し、配列化して後段 (
reduce_joinなど) で利用
- CSV から指定列だけを射影し、配列化して後段 (
chunk_tokens:- 単語ベースの近似トークン分割(
maxTokensとoverlap指定)
- 単語ベースの近似トークン分割(
sub_map:concurrency指定で subRLM 呼び出しを並列化(出力順は入力順を維持)
これにより、slice/find の都度全文スキャンではなく、1回の parse 後に構造アクセスを繰り返せる。
DocStore 抽象
RLMOptions.docStoreFactoryで文書バックエンドを差し替え可能- デフォルトは
InMemoryDocStore - MCP 経由で外部文書を読む場合は
MCPDocStore実装を利用
一般化可能性
- しやすい:
- ルール/構造があるタスク(needle 検索、表形式集計、定型抽出)
- 追加設計が必要:
- 自由記述生成・高い推論一貫性が必要なタスク
指標駆動の改善ループ(追加)
runImprovementLoop を使うと、次のようなタスクを同じパターンで扱えます。
- 実ベンチ指標を使ったリファクタリング候補の比較
- 大規模 lint 修正候補の採用/棄却(テスト失敗を制約でブロック)
import {
runImprovementLoop,
type ImprovementPolicy,
type MetricSnapshot,
} from './src/index.ts';
const policy: ImprovementPolicy = {
objectives: [
{ key: 'latencyP95', direction: 'minimize', weight: 1 },
{ key: 'throughput', direction: 'maximize', weight: 0.2 },
{ key: 'lintErrors', direction: 'minimize', weight: 2 },
],
constraints: [{ key: 'testFailures', comparator: 'eq', value: 0 }],
minScoreDelta: 1,
};
const baseline: MetricSnapshot = {
metrics: {
latencyP95: 120,
throughput: 100,
lintErrors: 80,
testFailures: 0,
},
};
const report = await runImprovementLoop({
baseline,
policy,
candidates: [{ id: 'cand-a', input: '...' }, { id: 'cand-b', input: '...' }],
evaluate: async (candidate) => {
// ここで実際に benchmark/lint/test を実行して metrics を返す
return {
metrics: await collectMetricsForCandidate(candidate),
};
},
});
console.log(report.bestAccepted?.candidate.id);constraints[].source は absolute | delta | ratio | delta_ratio を使えます。
ratio は metric / baseline、delta_ratio は (metric - baseline) / baseline です。
共通ハーネス(一般化)
flatbuffers と lint の long-run スクリプト共通部分は、以下に切り出しています。
runLongRunProgram:- long-run の実行サイクル(planner -> candidate 反復 -> report 出力)を共通化
createMetricSymbol:metricKeyとcandidateを受けて、評価関数を呼び、数値メトリクスを返す- candidate ごとのキャッシュにも対応
selectUntriedCandidates:- 既に評価済みの candidate id を除外して次ラウンド候補を作る
parseCLIKeyValues/parsePlannerProvider/parsePositiveInt:- CLI引数パースの共通化
Plan Mode(追加)
既存エージェントと組み合わせる場合は runPlannedRLM で、
- 入力を LLM で
RLMPlannerPlanに変換 - plan に従って
runRLMまたはrunLongImprovementLoopを実行
できます。
import { runPlannedRLM, OpenAIProvider } from './src/index.ts';
const planner = new OpenAIProvider({ model: 'gpt-4.1-mini' });
const executor = new OpenAIProvider({ model: 'gpt-4.1-mini' });
const out = await runPlannedRLM({
input: 'GitHub issue数を返して',
prompt: 'ignored',
plannerLLM: planner,
executorLLM: executor,
symbols: {
github_open_issues: async () => 42,
},
});
if (out.mode === 'single') {
console.log(out.result.final);
}FlatBuffers で long-run を試す
pnpm flatbuffers:longrun明示指定する場合:
pnpm node scripts/flatbuffers_long_run.ts \
--planner-provider mock \
--repo /Users/mz/ghq/github.com/google/flatbuffers \
--candidate-limit 2 \
--max-iterations 2 \
--repetitions 2 \
--out eval/report.flatbuffers.longrun.mock.json長時間ラン(例: GitHub open issue を減らす)では runLongImprovementLoop を使います。
import {
buildPolicyFromMetricSymbols,
collectMetricSnapshotBySymbols,
runLongImprovementLoop,
} from './src/index.ts';
const objectives = [
{
key: 'openIssues',
direction: 'minimize' as const,
read: async ({ state }: { state: { repo: string } }) =>
getOpenIssues(state.repo),
},
];
const policy = buildPolicyFromMetricSymbols({
objectives,
minScoreDelta: 1,
});
const report = await runLongImprovementLoop({
baseline: { metrics: { openIssues: await getOpenIssues('mizchi/rlm') } },
policy,
initialState: { repo: 'mizchi/rlm' },
maxIterations: 30,
stopWhenNoAccept: true,
generateCandidates: async () => proposeCandidatesFromAgent(),
evaluate: async (candidate, ctx) =>
collectMetricSnapshotBySymbols({
candidate,
iteration: ctx.iteration,
state: ctx.state,
objectives,
}),
});Lint 修正で long-run を試す
pnpm lint:longrun -- \
--repo /path/to/your/repo \
--lint-command "pnpm exec eslint . --format json" \
--test-command "pnpm test" \
--candidate-limit 4 \
--max-iterations 2 \
--out eval/report.lint.longrun.json候補を外部定義する場合(--candidates-file):
[
{
"id": "eslint-fix",
"description": "default --fix",
"commands": ["pnpm exec eslint . --fix"]
},
{
"id": "eslint-fix-problem",
"description": "problem only",
"commands": ["pnpm exec eslint . --fix --fix-type problem"]
}
]セットアップ
pnpm install
pnpm test
pnpm checkMoonBit 版(moonbit/):
moon -C moonbit check
moon -C moonbit testMoonBit 側は improve/* に加えて、run_rlm / run_planned_rlm / parse_eval_jsonl と mizchi/llm ベースの run_rlm_with_openai / run_planned_rlm_with_openai まで移植済み。
評価実行
1) ローカル確認(mock)
pnpm eval
pnpm eval:pure2) OpenAI で実評価
export OPENAI_API_KEY=...
pnpm eval:openai
pnpm eval:openai:pure詳細レポート保存:
pnpm node scripts/eval.ts \
--provider openai \
--model gpt-4.1-mini \
--cases eval/cases.sample.jsonl \
--profile hybrid \
--out eval/report.json3) アブレーション(pure vs hybrid)
pnpm eval:ablation
pnpm eval:ablation:openaipure: ヒューリスティック無しhybrid: 後処理・早期終了ヒューリスティック有り
ケース形式(JSONL)
1行1ケース。
{"id":"needle-1","prompt":"...","query":"...","expected":"...","metric":"exact"}フィールド:
id: ケースIDprompt: 入力文書query: 質問expected: 期待解metric:exactまたはcontains(省略時exact)budget: RLM のケース別 budget 上書き(任意)
