Recursive Language Models (RLM)

repository·main·Indexed 20 days ago

https://github.com/grishahq/recursive-llm

A Python implementation for efficient long-context processing (100k+ tokens). RLM reduces token usage and avoids context rot by storing large contexts as variables in a Python REPL worker process, allowing LLMs to recursively explore, search, and partition data. It supports 100+ providers via LiteLLM, including OpenAI, Anthropic, and local setups like Ollama and llama.cpp. Features include asynchronous API calls, structured trajectory logging, cost optimization via hybrid model setups, and a deterministic final answer validator.

Tokens
16.3K
Snippets
58
Records
65
Agent score
69%

What's inside recursive-llm

  1. When to use RLM vs. direct completion

    main

    Based on benchmark performance and interpretation, choose your approach based on context size and task requirements:

    • Direct Completion: Use for short contexts that fit comfortably within the model's native context window.
    • Recursive Language Models (RLM): Use when exact computation over a large context is required. RLM keeps the bulk of the context outside of the model prompts, using Python to perform aggregation and management, which prevents context overflow and manages costs.
  2. Understanding RLM recursion and depth

    main

    The max_depth parameter controls the potential for recursive child-model calls. However, RLM does not always force recursion.

    In many successful runs, the system may operate entirely at the root REPL (max_depth_reached=0). This demonstrates that RLM's value is derived not just from child-model calls, but from its ability to externalize context and perform local computation (e.g., via Python) to handle large datasets.

  3. How the RLM REPL works

    main

    RLM operates by storing context as a variable in a Python REPL environment. The root LM receives the query and instructions, then explores the context using Python code.

    Key Characteristics:

    • Persistence: REPL variables persist between iterations.
    • Isolation: Each local step executes in an isolated subprocess. Print output is isolated per step.
    • Safety: Uses RestrictedPython and subprocesses for defense-in-depth. Arbitrary module imports are blocked; only re, json, math, datetime, and collections are exposed.
    • Timeouts: Non-terminating local code is terminated by repl_timeout. Time spent waiting for model subcalls does not count against the local Python timeout.
  4. How Recursive Language Models (RLM) work

    main

    RLM enables language models to process extremely long contexts (100k+ tokens) by storing the context as a Python variable in a spawned worker process (REPL) instead of including it directly in the prompt. This approach:

    • Allows the LM to recursively explore, search, and partition the context.
    • Reduces model token usage on large-context tasks.
    • Avoids "context rot" (performance degradation associated with very long prompts).

    Important Implementation Note: Because RLM uses a spawned worker process for isolated REPL execution, all executable Python scripts must use the standard if __name__ == "__main__": entry-point guard to ensure compatibility with Python's multiprocessing on spawn-based platforms.

    if __name__ == "__main__":
        # Your RLM logic here
        pass
  5. Run multi-document benchmarks

    main

    To evaluate RLM performance on large, multi-document corpora, you must first prepare the local environment by downloading the required artifacts and ensuring their hashes match the pinned values in benchmarks/multi_document.py.

    Follow these steps:

    1. Create a temporary directory for the documents.
    2. Download the corpora (War and Peace, 9/11 Commission Report, and Python 3.14 documentation).
    3. Convert the 9/11 Commission PDF to text using pdftotext.
    4. Execute the benchmark script specifying the model, the directory containing the documents, the number of runs, a label, and the output format.
    mkdir -p /tmp/rlm-multi-document
    
    curl -L https://www.gutenberg.org/files/2600/2600-0.txt \
      -o /tmp/rlm-multi-document/war-and-peace.txt
    
    curl -L https://www.govinfo.gov/content/pkg/GPO-911REPORT/pdf/GPO-911REPORT.pdf \
      -o /tmp/rlm-multi-document/911-commission-report.pdf
    
    pdftotext -layout /tmp/rlm-multi-document/911-commission-report.pdf \
      /tmp/rlm-multi-document/911-commission-report.txt
    
    curl -L https://docs.python.org/3.14/archives/python-3.14-docs-text.zip \
      -o /tmp/rlm-multi-document/python-3.14-docs-text.zip
    
    python benchmarks/multi_document.py deepseek/deepseek-v4-flash \
      /tmp/rlm-multi-document --runs 3 --label baseline --jsonl results.jsonl
  6. Run reproduction benchmarks for different modes

    main

    You can use benchmarks/compare_same_model.py to compare the performance of a model in direct mode (sending the full context) versus rlm mode (using recursive language model decomposition).

    Available modes and task types:

    • Small deterministic tasks: Compare direct vs rlm with a specific max-depth.
    • 100k-character tasks: Use --generated-chars 100000 and a --seed to test long-context correctness.
    • Large scale checks: Use --generated-chars 1000000 to test RLM at a 1M-character scale.
    • Real document tasks: Run against a specific local file (e.g., a text file from Project Gutenberg).
    # Small deterministic tasks, three repetitions each
    python benchmarks/compare_same_model.py MODEL --full --runs 3 --mode direct
    python benchmarks/compare_same_model.py MODEL --full --runs 3 --mode rlm --max-depth 2
    
    # One 100k-character deterministic task, three repetitions
    python benchmarks/compare_same_model.py MODEL --generated-chars 100000 --seed 2026 --runs 3 --mode direct
    python benchmarks/compare_same_model.py MODEL --generated-chars 100000 --seed 2026 --runs 3 --mode rlm --max-depth 2
    
    # A larger RLM-only scale check
    python benchmarks/compare_same_model.py MODEL --generated-chars 1000000 --seed 2026 --runs 3 --mode rlm --max-depth 2
    
    # A SHA-pinned public-domain real document
    curl -L https://www.gutenberg.org/files/2600/2600-0.txt -o /tmp/war-and-peace-2600-0.txt
    python benchmarks/war_and_peace.py MODEL /tmp/war-and-peace-2600-0.txt --max-depth 2
  7. Quick Start: Initialize and process long context with RLM

    main

    To use RLM, import the RLM class, initialize it with a model name, and call .complete() with your query and the long context document. The context is stored as a variable rather than being injected into the prompt string.

    from rlm import RLM
    
    def main():
        # Initialize with any LLM
        rlm = RLM(model="gpt-5-mini")
    
        # Process long context
        result = rlm.complete(
            query="What are the main themes in this document?",
            context=long_document,
        )
        print(result)
    
    if __name__ == "__main__":
        main()
  8. Install Recursive Language Models (RLM) from source

    main

    Since the package is not yet published to PyPI, you must install it directly from the GitHub repository.

    1. Clone the repository.
    2. Install in editable mode using pip install -e ..
    3. Alternatively, install with development dependencies using pip install -e ".[dev]".

    Requirements:

    • Python 3.9 or higher
    • An API key for your chosen LLM provider (OpenAI, Anthropic, etc.) or a local model setup (Ollama, llama.cpp, etc.).
    # Clone the repository
    git clone https://github.com/grishahq/recursive-llm.git
    cd recursive-llm
    
    # Install in editable mode
    pip install -e .
    
    # Or install with dev dependencies
    pip install -e ".[dev]"
  9. Manage execution state with RunState

    main

    The RunState class manages the per-invocation state shared within a single RLM recursion tree. It tracks usage statistics, budget constraints, iteration counts at specific depths, and a chronological trajectory of events occurring during the run. It is designed to be thread-safe for recording events and node IDs.

    from rlm.run_state import RunState
    from rlm.budget import RunBudget
    from rlm.stats import UsageTracker
    
    # Initialize RunState with required components
    state = RunState(
        usage=UsageTracker(),
        budget=RunBudget(),
        event_handler=lambda event: print(f"Event: {event.kind}")
    )
  10. Configure API keys for LLM providers

    main

    RLM supports 100+ providers via LiteLLM. You can configure keys in two ways:

    1. Environment Variables: Create a .env file (copy from .env.example) and add your keys:
      OPENAI_API_KEY=sk-...
      DEEPSEEK_API_KEY=...
    2. Directly in Code: Pass the api_key argument to the RLM constructor.

    Hybrid Models: Use LiteLLM provider prefixes (e.g., deepseek/deepseek-v4-flash) to allow RLM to automatically select the correct API key for different models in a hybrid setup.

    # Using environment variables (via .env)
    rlm = RLM(model="gpt-5-mini", recursive_model="deepseek/deepseek-v4-flash")
    
    # Passing key directly
    rlm = RLM(model="gpt-5-mini", api_key="sk-...")
  11. How RLM depth semantics work

    main

    RLM uses a depth-based model to control the complexity and capability of the agent. The max_depth parameter determines how many levels of recursion are allowed:

    1. Depth 0: The root agent has access to a REPL (Python execution environment) but cannot make subcalls to other LLMs via the RLM interface. It can only execute code or return a final answer.
    2. Depth 1: The agent can make subcalls to a plain Language Model (LM) using _call_leaf, which returns a direct answer without a REPL loop.
    3. Depth 2: The agent can instantiate a child RLM instance. The boundary of this child RLM falls back to a plain LM call.

    This hierarchy allows you to control the computational budget and complexity of the reasoning process.

  12. Configure RLM limits and parameters

    main

    The RLM constructor accepts several configuration options to control depth, iteration limits, timeouts, and budget constraints.

    Key Configuration Options:

    • max_depth: Controls recursion levels. 0 is root only; 1 (default) allows root to call a plain LM; 2 allows one child RLM level.
    • max_iterations: Maximum REPL iterations per RLM.
    • repl_timeout: Hard timeout for each local Python step.
    • max_concurrent_subcalls: Bounds batch concurrency.
    • max_total_calls: Exact provider-call cap for the full recursion tree.
    • max_total_tokens: Stop after reported usage crosses this value.
    • max_total_cost_usd: Stop after reported cost crosses this value.
    • max_elapsed_seconds: Deadline shared by root and child calls.
    • max_retries: Retry transient provider failures (default is 0). Note: RLM disables hidden LiteLLM retries; use this parameter instead.
    • repl_memory_limit_mb, repl_cpu_time_limit_seconds, repl_max_open_files: POSIX-specific worker-process limits.
    rlm = RLM(
        model="gpt-5-mini",
        max_depth=2,                 # One child RLM level, then a plain-LM fallback
        max_iterations=20,           # Maximum REPL iterations per RLM
        repl_timeout=5,              # Hard timeout for each local Python step
        max_output_chars=2000,       # Observation truncation limit
        max_concurrent_subcalls=4,   # Bound batch concurrency
        max_total_calls=24,          # Exact provider-call cap for the full recursion tree
        max_total_tokens=100_000,    # Stop after reported usage crosses this value
        max_total_cost_usd=0.10,     # Stop after reported cost crosses this value
        max_elapsed_seconds=300,     # Deadline shared by root and child calls
        max_retries=2,                # Retry transient provider failures; default is 0
        retry_backoff_seconds=1.0,   # Exponential retry delay; Retry-After is respected
    )