Create a run trace
Usage
gr_trace(
run_id = NULL,
meta = list(),
system = NULL,
owner = NULL,
run_by = NULL,
purpose = NULL,
approved_by = NULL,
governor = NULL
)
# S3 method for class 'gr_trace'
as.data.frame(x, row.names = NULL, optional = FALSE, ...)Arguments
- run_id
Optional identifier; generated when omitted.
- meta
Named list of run-level metadata.
- system, owner, run_by, purpose, approved_by
Who the run is for and who ran it: the system it belongs to, the person or team accountable for that system, the person who ran it, what it was for, and who approved it. Each is one string. One left
NULLis taken fromgr_options("actor"), and isNAwhen that does not name it either.- governor
An
hr_governor()holding the limits every request recorded into this trace is checked against before it is sent, orNULL(the default) to read them fromgr_options()at each check.- x
A
gr_trace.- row.names
Optional row names for the result.
- optional, ...
Ignored; part of the
as.data.frame()generic.
Value
A gr_trace. It is an environment, so it accumulates by reference:
pass the same trace to several calls and they all record into it. Fields:
run_id, started, meta, actor, config, steps, calls,
cached, tokens_in, tokens_out, embed_tokens, errors,
budget_stop, stop_reason, spent_usd, replayed_usd, refusals,
governor.
actor holds the five fields above. config is a snapshot of the
options in force when the trace was made (gr_options(), with the values
of api_headers left out, since they can hold credentials) and the R
version, so a saved trace says what it ran under. as_json() writes
started in UTC, as ISO 8601 with a trailing Z.
cached counts the calls answered from a gr_cache() or a
gr_replay_client() rather than the network, so calls - cached is what
the run paid for. calls includes requests to an embeddings endpoint,
recorded as steps labelled "embed.request". Their tokens are counted in
embed_tokens, not in tokens_in, so tokens_in and tokens_out stay
the size of the model calls' prompts and replies.
errors has one entry per request that failed: its step, label and
error. A failure the run recovered from without losing any input also
carries recovered = TRUE: the document was read in full, and
gr_read_many() does not count it as failed. Only some fallbacks mark the
answer partial. An embeddings request replaced by gr_embed()'s lexical
fallback does when the vectors ranked the chunks a reader sent (the
retrieve, rerank and iterative readers). The same fallback while
gr_segment() made semantic cuts, and a proposition batch kept as
written, leave the answer unmarked and say so in its $warnings. A
contextual header that could not be written leaves no mark on the answer
at all, and this entry is the record of it.
budget_stop is TRUE once a limit stopped the run, and stop_reason
says which: "calls" for max_calls, "cost" for max_cost_usd,
"tokens" for max_tokens_total, and "output", "model" or
"unpriced" for the other limits of hr_governor() (see gr_options()).
refusals has one entry per request the governor did not send: its
label, model, the limit it would have passed, its worst-case cost
worst_usd, the run's calls and spent_usd at that moment, and the
message. A refused request is not a call and has no step. spent_usd
is what the calls so far cost. A call to a model with no registered price
(sent under allow_unpriced = TRUE) adds nothing to it, so
gr_trace_cost() is the full account. replayed_usd is what the calls a
gr_replay_client() answered cost when they were recorded, counted when
the spending limit stopped the recorded run: nothing was paid for them,
but max_cost_usd is checked against spent_usd + replayed_usd, so the
replay stops at the same call. A call answered from a gr_cache() adds to neither, so a
re-run through a warm cache under the same limit reads further than the
run that filled it.
as_json() and gr_trace_save() write budget_stop, stop_reason,
spent_usd and replayed_usd, so a saved run says whether it was cut
short, and refusals and governor when there are any.
as.data.frame() on a trace returns one row per request; see below.
Steps
Each request is one entry in steps. Besides its label, prompt, reply and
the model it asked for (model), a step records:
paramsWhat the call asked for: the parameters a
gr_mock_client()orgr_backend_client()handler is given, which is also what agr_replay_client()looks a call up by.params_sentThe request as it went out, every default resolved:
model,api,base_url,max_output(for a reasoning model, the larger cap its request is sent with; seegr_call()),temperature(NAwhen none was sent),effort,schema_name, and SHA-256 hashes of the JSON schema (schema_hash) and of the extra body fields the client and the call added (extra_body_hash). For agr_client()it is read from the request body; for a handler, from the parameters the handler was given, which carry no extra body fields. A call answered from agr_cache()records what would have been sent, and one a replay answered records what the recorded call sent.tokensinputandoutput, plusreasoningandcached_inputwhen the provider reported them (they are parts ofoutputandinput, not additions), andprovenance:"reported"when the provider gave the counts,"counted"when they were counted here with the"tiktoken"tokenizer,"estimated"when they were estimated with another one, andNAfor a reply cached or recorded before provenance was kept.served_model,response_id,fingerprintThe model the provider said answered, its response id and its system fingerprint, where it returned them, else
NA. None of them is part of a cache or replay key.attemptsHow many requests were sent for this one, retries included: 0 when a cache or a recording answered it.
started_utc,ended_utcWhen the request started and ended, ISO 8601 in UTC with a trailing
Z.
One row per request
as.data.frame(trace) has one row for each request the run made, in the
order they were made, and none for local steps such as segmentation:
stepThe step's number in
trace$steps, where the full record is.documentThe document the request was about, when the run recorded one: the file name, web address or
"<inline text>".recipeThe recipe the request belonged to, when recorded.
stageWhat the request was for, such as
"map.answer"or"reduce".model,ok,cachedThe model, whether a usable reply came back, and whether it came from a
gr_cache()or agr_replay_client().tokens_in,tokens_outThe size of the prompt and the reply.
usdWhat the request cost, 0 when it came from a cache or failed without sending any tokens.
NAwhen the model has no registered price, as ingr_trace_cost(), whose total the column adds up to.secondsHow long the request took, retries included.
NAfor a trace written by a version of readgpt that did not time requests.errorThe error, or
NA.prompt,replyThe messages sent, each as
"[role] text", and the text that came back.effortThe reasoning effort the request was sent with,
NAfor one sent none. Present only when some request in the trace carried one (seeeffortingr_read_spec()).tokens_reasoning,tokens_cached_in,served_modelThe reasoning and cached input tokens the provider reported and the model it said answered,
NAwhere it did not. Each is present only when some request in the trace has it.
A request's usd prices its cached input tokens at the model's cached
input rate when gr_register_model() gave it one, and at the input rate
otherwise.
Examples
tr <- gr_trace(meta = list(purpose = "demo"))
cl <- gr_mock_client(function(m, p) "an answer")
ch <- gr_segment(gr_example(), list(method = "sentence", max_tokens = 150))
#> Using cached ingestion for this document + settings.
#> Segmenting with 'sentence' (cap 150 tokens, overlap 0).
invisible(gr_read(ch, "What was revenue?", cl, "map_reduce", trace = tr))
#> Reading with 'map_reduce' (all|N+logN|tree) over 5 chunk(s).
print(tr)
#> <gr_trace run_20261106103547.855_999cdf> 7 steps, 6 model calls, 1118 in / 36 out tokens, 0 error(s)
#> steps: map.answer x5, preflight x1, reduce x1
#> cost: $0.0000 across mock-model
#> usage: estimated locally for 100% of requests (the provider reported none)
#> started: 2026-11-06T10:35:47.855Z
# One row per request, with what each cost and how long it took.
reqs <- as.data.frame(tr)
reqs[, c("step", "stage", "tokens_in", "tokens_out", "usd", "seconds")]
#> step stage tokens_in tokens_out usd seconds
#> 1 2 map.answer 201 6 0 0
#> 2 3 map.answer 210 6 0 0
#> 3 4 map.answer 187 6 0 0
#> 4 5 map.answer 193 6 0 0
#> 5 6 map.answer 163 6 0 0
#> 6 7 reduce 164 6 0 0