rMatch
Automated matching of recall segments to story segments.
rMatch matches each segment of a participant's recall to the corresponding segment(s) in the original story, using a large language model. Runs fully locally with google/gemma-4-31B-it, and with slightly higher performance in the cloud with Claude:
| Model | short text (N=21) | long text (N=19) | movie transcripts (N=138) |
|---|---|---|---|
| google/gemma-4-31B-it | 0.84 | 0.78 | 0.67 |
| Claude Opus 4.6 | 0.87 | 0.8 | 0.7 |
Pearson r with human ratings.
Table of contents
- Table of contents
- Quickstart
- Installation
- Matcher object
- API keys
- Prompts
- Batch matching from files directly
- Benchmark results
- CLI
Quickstart
Local:
from rmatch import MatcherCuda
# MatcherCuda requires nvidia GPUs
matcher = MatcherCuda()
matches = matcher.match(
story_segments=["The cat sat on the mat.", "It purred softly."],
recall_segments=["A cat was on a mat."],
)
# [(0, [0])] — recall segment 0 matched story segment 0
Installation
pip install rmatch # API matchers + huggingface fallback
pip install rmatch[cuda] # NVIDIA GPU (vLLM)
pip install rmatch[mac] # Apple Silicon (MLX)
Requires Python 3.12 or 3.13.
Matcher object
The Matcher object is the main tool to do the matching:
| Matcher | When to use |
|---|---|
MatcherCuda |
Run locally on NVIDIA GPUs with vLLM (pip install rmatch[cuda]) |
MatcherAnthropic |
Best performance; Needs an Anthropic API key (default model: claude-opus-4-6) |
MatcherOpenai |
Cloud alternative; Needs an OpenAI API key (default: gpt-4.1) |
MatcherMac |
Run locally on Apple Silicon (pip install rmatch[mac]) |
MatcherHuggingface |
Local fallback; works on any local archtiecture, but is not resource efficient |
Each matcher implements a function matcher.match(story_segments, recall_segments) that expects:
story_segments— ordered list of story segments.recall_segments— ordered list of one participant's recall segment strings.
Optionally, pass prompt and parser to customize the prompt (see Prompts).
The function returns one entry per recall segment in the format of (recall_index, [story_indices]) tuples (0-based):
[
(0, [2, 5]), # recall segment 0 -> matches story segments 2 and 5
(1, []), # recall segment 1 -> no match
(2, [0]), # recall segment 2 -> matches story segment 0
]
MatcherCuda
The main matcher to run locally - if you have nvidia gpus available. 94GB of VRAM (across multiple gpus) was enough to run gemma-4-31B-it without quantization.
See here for quantized versions.
from rmatch import MatcherCuda
# install rmatch with `pip install rmatch[cuda]`
matcher = MatcherCuda()
matches = matcher.match(
story_segments=["The cat sat on the mat.", "It purred softly."],
recall_segments=["A cat was on a mat."],
)
# [(0, [0])] — recall segment 0 matched story segment 0
| Argument | Default | Notes |
|---|---|---|
model_name |
"gemma-4-31B-it" |
Override the default model |
max_retries |
10 |
Retries when the model output cannot be parsed |
api_key |
from .env / env |
See API keys |
window_size |
5 |
Recall context window |
max_new_tokens |
1024 |
Max tokens generated per segment |
max_model_len |
90000 |
Max sequence length (prompt + generation); lower to save GPU memory |
tensor_parallel_size |
auto | Number of GPUs to use. Auto will use all of them. |
gpu_memory_utilization |
0.90 |
Fraction of GPU memory to use, see vLLM |
MatcherAnthropic / MatcherOpenai
Use a cloud provider to do the matching. It's pretty inexpensive, and --dry_run will give you an approximate estimate of the cost. Usually a single recall doesn't cost more than 0.5$, even on frontier models.
from rmatch import MatcherAnthropic
# install rmatch with `pip install rmatch`
matcher = MatcherAnthropic(api_key="your_api_key")
matches = matcher.match(
story_segments=["The cat sat on the mat.", "It purred softly."],
recall_segments=["A cat was on a mat."],
)
# [(0, [0])] — recall segment 0 matched story segment 0
You can also put your API key in a .env file instead of passing it directly (see API keys).
| Argument | Default | Notes |
|---|---|---|
model_name |
"claude-opus-4-6" / "gpt-4.1" |
Override the default model |
max_retries |
10 |
Retries when the model output cannot be parsed |
api_key |
from .env / env |
See API keys |
window_size |
5 |
Recall context window |
dry_run |
False |
Estimate API cost without calling the API |
MatcherMac
You can try running the matching on your mac with apple silicon! You'll probably need a lot of unified memory, or use a quantized model. The standard unsloth/gemma-4-E4B-it-MLX-8bit model should run on a mac with 24GB of unified memory.
from rmatch import MatcherMac
# install rmatch with `pip install rmatch[mac]`
matcher = MatcherMac()
matches = matcher.match(
story_segments=["The cat sat on the mat.", "It purred softly."],
recall_segments=["A cat was on a mat."],
)
# [(0, [0])] — recall segment 0 matched story segment 0
| Argument | Default | Notes |
|---|---|---|
model_name |
"unsloth/gemma-4-E4B-it-MLX-8bit" |
Override the default model |
max_retries |
10 |
Retries when the model output cannot be parsed |
api_key |
from .env / env |
See API keys |
window_size |
5 |
Recall context window |
max_new_tokens |
300 |
Max tokens generated per segment |
MatcherHuggingface
The fallback matcher that should work on any platform. Can run the same models as the Cuda/Mac matcher, and will achieve the same matching performance, but will require considerably more computing resources and be a lot slower.
from rmatch import MatcherHuggingface
# install rmatch with `pip install rmatch`
matcher = MatcherHuggingface()
matches = matcher.match(
story_segments=["The cat sat on the mat.", "It purred softly."],
recall_segments=["A cat was on a mat."],
)
# [(0, [0])] — recall segment 0 matched story segment 0
| Argument | Default | Notes |
|---|---|---|
model_name |
matcher-specific | Override the default model |
max_retries |
10 |
Retries when the model output cannot be parsed |
api_key |
from .env / env |
See API keys |
window_size |
5 |
Recall context window |
quantization |
none | "4bit" or "8bit" to reduce memory |
batch_size |
64 |
Inference batch size |
max_new_tokens |
300 |
Max tokens generated per segment |
API keys
For MatcheAnthropic/MatcherOpenai you need the API key to access the models.
For all local matchers, you made need the API key in form of the HF_token - to allow you access to download a large language model from the hub.
rMatch will look for the API key in this order (first match wins):
api_keyargument in Python.envfile in the working directory- Environment variables in your shell
ANTHROPIC_API_KEY="your_api_key" # anthropic
OPENAI_API_KEY="your_api_key" # openai
HF_TOKEN="your_hf_token" # huggingface, mac, cuda (model download)
Prompts
All matchers share the same default prompt (DEFAULT_PROMPT in rmatch.matchers.matcher_base). You can pass your own prompt template to match(). It can contain the following slots, which are filled for every recall segment:
| Slot | Filled with |
|---|---|
{story_full} |
The full story as one string |
{story_segments} |
The numbered story segments (1. ..., 2. ...) |
{recall_window} |
The target recall segment marked as >>> ... <<<, with its neighbors |
Only these slots are replaced; any other braces in the template are left as-is.
my_prompt = """Story pieces:
{story_segments}
Recall:
{recall_window}
Which numbered story pieces does the >>> <<< clause express?
Answer with numbers in angle brackets, e.g. <1, 4>, or <NONE>."""
matches = matcher.match(story_segments, recall_segments, prompt=my_prompt)
The default parser (DEFAULT_PARSER) extracts numbers in angle brackets such as <3, 7, 12>, or <NONE>. If your prompt asks for a different output format, also pass a parser: a function that takes the raw model output and returns a set of 1-based story segment numbers, or None if the output cannot be parsed (the matcher then retries).
def my_parser(raw: str) -> set[int] | None:
...
matches = matcher.match(story_segments, recall_segments, prompt=my_prompt, parser=my_parser)
Batch matching from files directly
If your story and recalls are saved as .txt or .json files, you can match them as a batch with match().
from pathlib import Path
from rmatch import match, MatcherCuda
matcher_gemma = MatcherCuda()
results = match(
matcher=matcher_gemma,
story_file=Path("story.txt"),
recall_file=Path("recalls/"), # file or directory of subject files
)
Optionally, pass prompt and parser to customize the prompt (see Prompts).
This loads all subjects, runs matching, and writes a JSON results file next to your recall data with the following format:
{
"matcher_name": "anthropic",
"story_name": "story",
"story_segmentation": "lines",
"recall_segmentation": "lines",
"matches": {
"sub-001": [[0, [3, 7]], [1, [12]]],
"sub-002": [[0, [1]], [1, [5, 6]]]
}
}
Each subject maps to a list of [recall_segment_id, [matched_story_segment_ids...]] pairs.
Benchmark results
| Matcher | Testset | F1 | Precision | Recall | Pearson R |
|---|---|---|---|---|---|
| MatcherCuda:google/gemma-4-31B-it | alice | .84 | .8 | .89 | .83 |
| MatcherCuda:google/gemma-4-31B-it | monthiversary | .79 | .72 | .86 | .78 |
| MatcherCuda:google/gemma-4-31B-it | memsearch | .67 | .56 | .84 | .64 |
alice(medium text) are 21 recalls with a length of ~200 words, and a story length of ~700 words.monthiversary(long text) are 19 recalls with a length of ~1000 words, and a story length of ~4700.memsearch(short movie transcripts) are 138 recalls with a length of ~140 words, and a story transcript length of ~240 words.
Replicate results by downloading rBench:
git clone git@github.com:GabrielKP/rBench.git
Add to .env or environment:
BENCHMARK_ROOT="path/to/rBench"
Run:
uv run src/rmatch/evaluate.py {alice,monthiversary,memsearch}
CLI
If you prefer the command line over a Python script:
rmatch story.txt recalls/ --matcher anthropic
See rmatch --help for all options (model, window size, etc.).
Metadata
Release files for rMatch 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rmatch-0.6.0.tar.gz | 268.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rmatch-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 307.3 kB
Release files / rmatch-0.6.0.tar.gz
| Download URL | rmatch-0.6.0.tar.gz |
|---|---|
| Size | 268.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
35058b197e5b33db4a4dc18bfc98215257e4972fff8ec7a76672edddb79cc40a
|
|
BLAKE2b-256 checksum How to use checksums |
645dc0c9aba6926bd5366afb3330231626d06abb417d664857a9a99af327cf9c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / rmatch-0.6.0-py3-none-any.whl
| Download URL | rmatch-0.6.0-py3-none-any.whl |
|---|---|
| Size | 38.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a631650f196b6561542f75c0fb94c1deb906f33334210c225b19dcd4cc43d574
|
|
BLAKE2b-256 checksum How to use checksums |
9427bad592797df03edb6ecd691ea659fe91c0787a7f6eef62c277982f7b9ca3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log