Skip to content
ai-effect
Esc
↑↓navigate↵open⌘Jpreview

Tracing

The Effect spans every ai-effect call records, and how to see them.

Every ai-effect call records an Effect span, so a slow or failing model call shows up in whatever tracer your app already uses. Nothing is enabled or configured: the spans go to the Tracer provided where the call runs.

Spans

Call Span name Kind
generateText ai-effect.generateText internal
streamText ai-effect.streamText internal
ToolLoopAgent generate and stream invoke_agent {id}, or ai-effect.ToolLoopAgent.generate / .stream without an id internal
createAgentUIStream invoke_agent {id}, or ai-effect.createAgentUIStream internal
createUIMessageStream ai-effect.createUIMessageStream internal
embed, embedMany embeddings {modelId} client
rerank rerank {modelId} client
a tool’s execute execute_tool {name} internal

Attributes follow the OpenTelemetry GenAI semantic conventions:

Attributes Recorded on
gen_ai.operation.name, gen_ai.agent.name call spans
gen_ai.request.model, gen_ai.provider.name (mapped, for example google becomes gcp.gemini) text calls
gen_ai.request.temperature, top_p, top_k, max_tokens, frequency_penalty, presence_penalty, stop_sequences, seed, stream text calls, when set
gen_ai.response.id, gen_ai.response.model, gen_ai.response.finish_reasons text calls, once finished
gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, plus cache read, cache write and reasoning tokens when non-zero text calls, once finished
gen_ai.usage.input_tokens, gen_ai.embeddings.dimension.count embeddings
gen_ai.tool.name, gen_ai.tool.call.id, gen_ai.tool.type, gen_ai.tool.description tool spans
error.type, the AiError reason’s _tag any span that failed

Prompts, completions and tool inputs and outputs are not recorded.

See them

An Effect Tracer is a service, and a layer replaces it. This one prints each span when it ends:

import { Ai, generateText } from 'ai-effect';
import { Effect, Layer, Option, Tracer } from 'effect';

class PrintSpan extends Tracer.NativeSpan {
	override end(endTime: bigint, exit: Parameters<Tracer.NativeSpan['end']>[1]) {
		super.end(endTime, exit);
		const parent = Option.isSome(this.parent) ? this.parent.value.spanId : '-';
		console.log(this.name, { parent }, Object.fromEntries(this.attributes));
	}
}

const PrintTracer = Layer.succeed(
	Tracer.Tracer,
	Tracer.make({ span: (options) => new PrintSpan(options) }),
);

generateText({ model: 'openai/gpt-5', prompt: 'Hello!' }).pipe(
	Effect.provide(Layer.merge(Ai.layer, PrintTracer)),
	Effect.runPromise,
);

It prints a line such as this, with the attributes from the table above:

ai-effect.generateText { parent: "-" } {
  "gen_ai.operation.name": "invoke_agent",
  "gen_ai.response.finish_reasons": [ "stop" ],
  "gen_ai.usage.input_tokens": 3,
  "gen_ai.usage.output_tokens": 10,
  ...
}

To send spans to a backend, see OpenTelemetry.

Tools

Any tool with an execute that you pass to an ai-effect call gets an execute_tool {name} span, parented to the call’s span. That includes plain SDK tools, not only ones made with tool. Spans you create inside an Effect execute nest under it.

Streams

streamText, ToolLoopAgent.stream, createAgentUIStream and createUIMessageStream return before the model has finished, but their span does not end with the returning Effect. It ends when the SDK finishes or aborts, so its duration covers the whole stream. An interrupted stream ends its span as interrupted.

agent.raw is not traced

agent.raw is the SDK’s own agent. Calling it directly records no spans, and its callbacks are not Effects.

Was this page helpful?