How tracing works
Every agent run is a trace — a record of inputs, outputs, timing, cost, and the steps in between.
VevalSdk.RunAsync starts a trace, runs your agent, then ships the payload to Veval.
- Your agent calls
ctx.TrackStepAsync for each LLM call or sub-operation.
- The trace appears in your dashboard with full step detail.
RunAsync
Wraps a complete agent invocation. Sends a trace on success or error.
var result = await veval.RunAsync(
agentName: "my-agent",
callback: ctx => myService.ExecuteAsync(ctx),
input: userMessage // optional — stored on the trace
);
| Parameter | Type | Description |
|---|
agentName | string | Identifies the agent in the dashboard |
callback | Func<VevalExecutionContext, Task<T>> | Your agent logic |
input | object? | Arbitrary input stored on the trace |
VevalExecutionContext
The context object passed into your agent. Do not instantiate directly — Veval creates it for you.
| Member | Description |
|---|
TraceId | Unique ID for this run (tr_...) |
Input | The input passed to RunAsync |
TrackStepAsync(...) | Records a step |
SetMetadata(key, value) | Attaches trace-level metadata |
TrackStepAsync
Records a single step within a trace.
// Simple overload — no handle needed
var summary = await ctx.TrackStepAsync("summarize", input: text, async () =>
{
return await llm.Summarize(text);
});
// Handle overload — attach LLM metadata
var answer = await ctx.TrackStepAsync("answer", input: question, async handle =>
{
var response = await claude.Messages.CreateAsync(...);
handle.SetMeta("model", response.Model);
handle.SetMeta("tokens_in", response.Usage.InputTokens);
handle.SetMeta("tokens_out", response.Usage.OutputTokens);
handle.SetMeta("cost_usd", 0.0012m);
return response.Content[0].Text;
});
handle.SetMeta(key, value) accepts these well-known keys, plus any custom string:
| Key | Type | Description |
|---|
model | string | Model name (e.g. claude-sonnet-4-6) |
tokens_in | int | Input token count |
tokens_out | int | Output token count |
cost_usd | decimal | Cost in USD |
type | string | Step type — use "tool" for tool calls |
| (custom) | object | Any other key stored in step metadata |
Nested steps
Steps can be nested by passing the StepHandle to a child TrackStepAsync call.
var result = await ctx.TrackStepAsync("pipeline", input: query, async handle =>
{
var classified = await ctx.TrackStepAsync("classify", input: query, async () =>
await llm.Classify(query));
var answered = await ctx.TrackStepAsync("answer", input: classified, async () =>
await llm.Answer(classified));
return answered;
});
Nested steps appear as a tree in the dashboard, letting you see exactly where time and cost are spent.
Attach arbitrary key/value pairs to the whole trace (not a step):
ctx.SetMetadata("user_id", userId);
ctx.SetMetadata("session", sessionId);
ctx.SetMetadata("region", "us-east-1");