Skip to main content

lure: Library Usage REwards

Reward functions that score LLM-generated code for reusing external libraries instead of reimplementing them, for reinforcement learning (e.g. GRPO) or evaluation.

  • Static and safe: code is parsed with Python's ast module and never executed.
  • No dependencies: only the standard library is used, and Python 3.10+ is supported.
  • Hard to fool: a library only counts as used when the code actually references what it imported. Unused imports, names that are rebound, imports inside strings, code in the reasoning, and truncated code blocks earn no usage credit.
pip install lure

Reward functions

from lure import create_library_reward

reward_func = create_library_reward()

response = """<think>A CSV parser already exists, I should reuse one.</think>
```python
import pandas as pd

def load(path):
    return pd.read_csv(path)
```"""

reward_func(response, stop_reason="stop")  # 2.0

The reward is the sum of the weights of the components the response satisfies:

Component Default weight Satisfied when
correct_format 0.25 the response has reasoning and an answer, and wasn't truncated (stop_reason="length")
passes_syntax_check 0.25 the answer contains python code, and every code block is valid
external_lib_imported 0.5 the code imports an external (non-standard-library) library
external_lib_used 1.0 the code imports an external library and actually uses it
appropriate_lib_used 0.0 the code uses one of appropriate_libraries

Every weight is a keyword argument, and setting a weight to 0 switches that component off:

reward_func = create_library_reward(
    external_lib_used=1.0,
    appropriate_lib_used=0.5,
    appropriate_libraries=["requests", "httpx"],  # import or pypi names, any case
    local_modules=["app"],                        # the project's own code is never a library
    require_reasoning=False,                      # for non-thinking models
)

# local_modules and appropriate_libraries can also be set per response
reward_func(response, stop_reason="stop", appropriate_libraries=["pandas"])

# see which components a response satisfied, e.g. for logging
reward_func.components(response)

# the highest possible reward, for normalising to [0, 1]
reward_func.max_reward

When each dataset item has its own local modules or appropriate libraries, give them as a dict keyed by item id, and pass each response's item_id when scoring it:

reward_func = create_library_reward(
    appropriate_lib_used=0.5,
    local_modules={"task-1": ["app"], "task-2": ["server", "utils"]},
    appropriate_libraries={"task-1": ["requests", "httpx"], "task-2": ["pandas"]},
)

reward_func(response, stop_reason="stop", item_id="task-2")

A list passed with the response takes priority over the dict. An item_id that is missing or not in the dict raises an error rather than using no names. Otherwise the project's own imports would silently count as external libraries.

stop_reason is how generation ended, e.g. vLLM's output.outputs[0].finish_reason or the OpenAI API's choice.finish_reason. If it's left out, the response is assumed not to be truncated.

With TRL's GRPOTrainer

from lure import create_library_reward, create_trl_reward
from trl import GRPOTrainer

trainer = GRPOTrainer(
    model=model,
    reward_funcs=create_trl_reward(
        reward=create_library_reward(),
        eos_token_id=tokenizer.eos_token_id,  # detects truncated completions
    ),
    train_dataset=dataset,  # optional columns: local_modules, appropriate_libraries
    ...
)

If the reward was created with per-item dicts, each row's item_id is read from the dataset's id column (change it with item_id_column=...).

Parsing responses

parse_response exposes everything the reward is based on:

from lure import parse_response

parsed = parse_response(response, stop_reason="stop", local_modules=["app"])

parsed.reasoning         # text before </think>
parsed.answer            # text after </think>
parsed.code_blocks       # the python code blocks in the answer, each analysed
parsed.external_imports  # frozenset({"pandas"})
parsed.external_used     # frozenset({"pandas"})
parsed.stdlib_imports    # frozenset()
parsed.valid             # True: there is code and it all parses
parsed.truncated         # False
parsed.longest_block     # the longest code block, e.g. to run it

The rules it follows:

  • Reasoning is everything before the first </think> (also </thinking>, </reasoning>, </thought>, or your own tags, see below), and is never analysed. The opening tag is optional, because some chat templates put it in the prompt. An opening tag with no closing tag means there is no answer.
  • Code is every complete fenced block labelled python, py or python3, or left unlabelled. Other languages and unterminated fences are ignored. If the answer has no fences but is entirely valid python, the whole answer is used as the code.
  • Libraries are top-level import names in their original case (e.g. PIL). The standard library, relative imports, __future__, and local_modules are never counted as external. A library is used when a single code block imports it and then references it. Referencing it in a different block doesn't count, and neither does a name rebound by assignment, loop variable, argument or similar.

Models that mark their reasoning with other tags can pass them as (opening, closing) pairs. They are matched literally, ignoring case, and replace the defaults. create_library_reward takes the same reasoning_tags argument:

parse_response(response, reasoning_tags=[("[THINK]", "[/THINK]")])
parse_response(response, reasoning_tags=("<|begin_of_thought|>", "<|end_of_thought|>"))

The building blocks are also available on their own: split_reasoning, extract_code_blocks, analyse_code, and pip_package (e.g. pip_package("PIL") == "Pillow").

Citation

Paper coming soon.

Licence

lure is released under the MIT licence.

Release files for lure 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lure 0.1.0
File Size Uploaded
lure-0.1.0.tar.gz 17.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lure 0.1.0
File Interpreter ABI Platform
lure-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 34.9 kB

Release files / lure-0.1.0.tar.gz

Download URL lure-0.1.0.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0d581ff0936db4f4df90feb0d87f64345fd6eb24e95a201573fad1f96ad3eb23
BLAKE2b-256 checksum
How to use checksums
ea538db53f97120ebd04f67ed89b705eeda67e95de15d441c53829576f689b59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / lure-0.1.0-py3-none-any.whl

Download URL lure-0.1.0-py3-none-any.whl
Size 17.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
71fa3513562f7ea88055c416b67ab2a749cfe221c290a6ec6b77fc9c333e7d8e
BLAKE2b-256 checksum
How to use checksums
393b00f4d827c8f8a73a6e302b814390973eb98034a1520af043a4b75d1a15aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page