Skip to main content
A reward function turns a model completion into a number. GRPO uses these scores to compute advantages: completions above the group mean get reinforced, completions below get penalized. Rewards are not a separate resource on Veri. You attach the function’s Python source directly to the training job at submit time; the platform stores it with the job and it lives and dies with the job. There is nothing to upload, name, or clean up.

Format

Veri runs reward functions in the trl shape, the format the production GRPO runner uses. At submit, the platform checks that each source parses as Python and defines a function; a violation rejects the job with HTTP 400 before anything is billed. Loading and invocation happen in the open-source runtime: see load_reward_function in training_runtime.py (and score_episode in harness_rollout.py for how harness trajectories are scored) if you need to check exactly how your function is called.

TRL format

Receives a batch of completions, returns one score per completion.
  • completions — list of strings OR list of message-dict lists (depending on how the runner formats prompts)
  • answer — list of ground-truth strings (from the dataset’s answer column)
  • Return — list of floats, same length as completions

Attach it to a job

The reward rides the job submission itself. Pick your surface:
From the CLI, veri jobs create configs/train.toml reads [reward].file, or override it inline with --reward ./reward.py (a relative path resolves against the config file’s directory, like [reward].file). The CLI runs an AST lint before submitting (the file parses as Python and defines at least one function), so a broken file fails locally instead of at the API. A method=grpo or method=grpo_harness job requires a reward. sft_text and dpo jobs must omit it (the dataset is their training signal), and so must custom_script jobs, which own their training loop. The job response carries reward_count (the number of attached sources; null for reward-free methods). Limits: up to 8 sources per job, 256 KiB each, and every source must define a function. Violations are HTTP 400 at submit.

Where rewards run

Your reward is imported and called inside the training process on the GPU machine. It can use the Python standard library and the packages already installed on the training image; there is no way to declare extra dependencies. There is no separate sandbox and no per-call timeout: the machine has network access, and a reward that hangs (for example on a slow network call) stalls the whole job. Keep rewards fast and deterministic, and avoid network calls. An exception raised by your reward fails the job with error.code = "training_error".

Composing rewards

A common pattern is to weight multiple signals:
Keep each component bounded so the total stays in a stable range ([0, 1] is conventional). GRPO normalizes scores within a group, so the absolute scale matters less than the spread.

Multiple weighted reward functions

Instead of summing signals inside one function, you can attach several reward sources and let GRPO combine them with weights. TRL’s GRPOTrainer scores each completion with every function and sums the results, scaled by reward_weights. This keeps each signal in its own file (independently testable) and lets you retune the mix without editing code. Each function has the standard trl signature and returns one score per completion. Pass the sources and matching weights:
reward_weights is optional; omit it to weight every function equally. When provided, its length must match reward_sources (the SDK and API reject a mismatch). A single reward_source still works for the one-reward case.

Viewing a job’s reward

Every job keeps the exact source it was submitted with. Click a training job on the dashboard to view the reward source it was submitted with, or fetch it programmatically:
Because the source is stored per job, rerunning an old experiment is copy-paste: read the source back, tweak it, submit a new job.

Shaped rewards for harness jobs

Harness-in-the-loop jobs score the trajectory’s final assistant message by default, the deploy-aligned choice, since at inference time the final answer is all that matters. When you need more signal (partial credit for fetching the right page, penalties for invalid tool calls), declare a trajectory parameter and the platform passes the full message history of the rollout’s final turn (protocol-shaped dicts, including tool_use / tool_result content), one history per completion:
Rewards that don’t declare trajectory are called exactly as before; the parameter is opt-in. One sharp edge with chatty agent runtimes (the Claude Agent SDK especially): because the default scores only the final message, instruct your harness to put the answer there (“finish with a last line of exactly ANSWER: <url>”) or a correct mid-trajectory answer can score zero.

Where to go next

GRPO algorithm

What the score actually does inside the training loop.

Submit a job

Wire reward_source into training_jobs.create.

Quickstart

End-to-end with the example math reward.