artifactr.telemetry¶
Tracing and metrics through the OpenTelemetry API. See Observability.
artifactr's tracing and metrics, through the OpenTelemetry API only (ADR-0027).
The API is the port. artifactr records spans and metrics under the artifactr scope and
never configures the SDK, calls Agent.instrument_all() or creates a backend client. With
no SDK configured, recording is a no-op. Applications configure the SDK themselves, or with
artifactr.otel.configure_telemetry (the [otel] extra), which is an adapter.
artifactr.telemetry.attributesnames every attribute artifactr sets.artifactr.telemetry.metricsis the metric registry and its cardinality policy.artifactr.telemetry.tracesnames the spans in artifactr's traces, and runs polling untraced.
The metric registry¶
Metric
dataclass
¶
Metric(
name: str,
instrument: Instrument,
unit: str,
description: str,
attributes: frozenset[str] = frozenset(),
scope: str = SCOPE,
buckets: tuple[float, ...] | None = None,
)
One metric: what it measures, and the attributes it may carry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The OpenTelemetry metric name. |
required |
instrument
|
Instrument
|
The instrument it is recorded with. |
required |
unit
|
str
|
The UCUM unit, or an annotation in braces such as |
required |
description
|
str
|
What it measures. |
required |
attributes
|
frozenset[str]
|
The attributes it may carry. Anything else is dropped when it is recorded. |
frozenset()
|
scope
|
str
|
The instrumentation scope that records it: |
SCOPE
|
buckets
|
tuple[float, ...] | None
|
For a histogram, the bucket boundaries it advises the SDK to use. |
None
|
METRICS
module-attribute
¶
METRICS: Mapping[str, Metric] = MappingProxyType(
{
metric.name: metric
for metric in (
COMMANDS,
COMMIT_DURATION,
TURNS,
TURN_DURATION,
RUNS,
TOOL_CALLS,
TOKENS,
MESSAGES,
ARTIFACT_CHANGES,
PROPOSALS,
FEEDBACK,
STREAM_CONNECTIONS,
STREAM_DISCONNECTS,
)
}
)
artifactr's own metrics, by name.
EXTERNAL_METRICS
module-attribute
¶
EXTERNAL_METRICS: Mapping[str, Metric] = MappingProxyType(
{
metric.name: metric
for metric in (
Metric(
"gen_ai.client.token.usage",
"histogram",
"{token}",
"Tokens per model request, by model and type.",
scope="pydantic-ai",
),
Metric(
"operation.cost",
"histogram",
"{USD}",
"Estimated cost per model request, by model.",
scope="pydantic-ai",
),
Metric(
"gen_ai.client.operation.time_to_first_chunk",
"histogram",
"s",
"Time to the first chunk of a streamed model response.",
scope="pydantic-ai",
),
Metric(
"http.server.request.duration",
"histogram",
"s",
"HTTP requests served, REST and MCP.",
scope="opentelemetry.instrumentation.fastapi",
),
Metric(
"http.server.active_requests",
"up_down_counter",
"{request}",
"HTTP requests in progress.",
scope="opentelemetry.instrumentation.fastapi",
),
Metric(
"http.client.request.duration",
"histogram",
"s",
"Outgoing HTTP requests, model providers included.",
scope="opentelemetry.instrumentation.httpx",
),
Metric(
"db.client.connections.usage",
"up_down_counter",
"{connection}",
"Database connections in the pool, by state.",
scope="opentelemetry.instrumentation.sqlalchemy",
),
)
}
)
Metrics recorded by pydantic-ai and the OpenTelemetry instrumentations that artifactr's dashboards read, by name. The HTTP metrics are the stable HTTP conventions' names.
MetricsDetail
module-attribute
¶
MetricsDetail = Literal['workspace', 'tenant', 'none']
How much tenancy detail metrics keep: tenant and workspace, the tenant only, or neither.
kept_attributes
¶
kept_attributes(
metric: Metric, detail: MetricsDetail
) -> frozenset[str]
Return the attributes of metric that a deployment keeps at a level of detail.
SCOPE
module-attribute
¶
SCOPE: Final = 'artifactr'
The instrumentation scope of artifactr's own spans and metrics.
SCOPED
module-attribute
¶
The tenancy attributes, which MetricsDetail limits.
Traces¶
Which spans are artifactr's, and polling that makes no traces. See ADR-0046.
TRACE_SCOPES
module-attribute
¶
TRACE_SCOPES: Final = frozenset(
{
SCOPE,
"pydantic-graph",
"mcp-python-sdk",
"opentelemetry.instrumentation.fastapi",
"opentelemetry.instrumentation.asgi",
"opentelemetry.instrumentation.sqlalchemy",
"opentelemetry.instrumentation.asyncpg",
"opentelemetry.instrumentation.httpx",
}
)
The instrumentation scopes whose spans make up artifactr's traces, besides the model calls: artifactr's own, pydantic-graph's, the MCP SDK's, and the FastAPI, ASGI, SQLAlchemy, asyncpg and httpx instrumentations'.
is_trace_scope
¶
Return whether spans of an instrumentation scope belong in artifactr's traces.
A sub-scope of one of TRACE_SCOPES, such as artifactr.workspace, does too.
untraced
¶
untraced() -> Generator[None]
Run a block untraced: every span started in it is a child of a span that is never sampled.
Under a parent-based sampler, the SDK's default, those spans are not recorded, so nothing started in the block is exported: the database queries of a poll, say. Metrics recorded in the block are recorded as usual, the instrumentations' own included. artifactr polls storage this way, in subscriptions and the feedback mirror, so an idle application sends no traces; the work a poll finds, such as a commit, is traced where it happens.
Don't commit or publish in the block: its trace ids are the unsampled parent's, so a
revision or envelope would record a trace that does not exist. A sampler that ignores the
parent, such as always_on or traceidratio, records the block's spans again, each
poll in a trace of its own.
Attributes¶
The attribute names artifactr puts on spans and metrics, in one place.
The OpenTelemetry GenAI conventions are still in development; if their names change, they
change here. attribution builds the attributes every span artifactr owns or wraps
carries: the thread as the session, the person as the user, and artifactr's own ids.
SESSION_ID
module-attribute
¶
SESSION_ID: Final = 'session.id'
The session: artifactr's thread id. Langfuse groups traces into sessions by it.
USER_ID
module-attribute
¶
USER_ID: Final = 'user.id'
The person a span acts for: a user actor's id.
GEN_AI_CONVERSATION_ID
module-attribute
¶
GEN_AI_CONVERSATION_ID: Final = 'gen_ai.conversation.id'
The GenAI conversation: artifactr's thread id, as pydantic-ai sets it on its own spans.
GEN_AI_OPERATION_NAME
module-attribute
¶
GEN_AI_OPERATION_NAME: Final = 'gen_ai.operation.name'
The GenAI operation, such as invoke_workflow.
GEN_AI_WORKFLOW_NAME
module-attribute
¶
GEN_AI_WORKFLOW_NAME: Final = 'gen_ai.workflow.name'
The name of a GenAI workflow: turn for artifactr's turns.
GEN_AI_TOKEN_TYPE
module-attribute
¶
GEN_AI_TOKEN_TYPE: Final = 'gen_ai.token.type'
input or output, for token counts.
LANGFUSE_OBSERVATION_TYPE
module-attribute
¶
LANGFUSE_OBSERVATION_TYPE: Final = (
"langfuse.observation.type"
)
How Langfuse shows a span, such as chain.
RUN_ID
module-attribute
¶
RUN_ID: Final = 'artifactr.run.id'
artifactr's run id, which spans a run's pauses. pydantic-ai's own run id is per attempt.
ACTOR_KIND
module-attribute
¶
ACTOR_KIND: Final = 'artifactr.actor.kind'
The kind of actor: user, agent, external_agent or system.
COMMAND_TYPE
module-attribute
¶
COMMAND_TYPE: Final = 'artifactr.command.type'
A command's type, such as edit_artifact.
OUTCOME
module-attribute
¶
OUTCOME: Final = 'artifactr.outcome'
What a command did: applied, proposed, resolved, recorded or rejected.
REJECTION
module-attribute
¶
REJECTION: Final = 'artifactr.rejection'
Why a command was rejected: the rejection's code, such as version_conflict.
ARTIFACT_KIND
module-attribute
¶
ARTIFACT_KIND: Final = 'artifactr.artifact.kind'
An artifact's registered type name.
ARTIFACT_VERSION
module-attribute
¶
ARTIFACT_VERSION: Final = 'artifactr.artifact.version'
The version an artifact is at after a change.
PATCH_SIZE
module-attribute
¶
PATCH_SIZE: Final = 'artifactr.patch.size'
How many operations or text edits a patch has.
CHANGE
module-attribute
¶
CHANGE: Final = 'artifactr.change'
What happened to an artifact: created, changed or archived.
PROPOSAL_ACTION
module-attribute
¶
PROPOSAL_ACTION: Final = 'artifactr.proposal.action'
What happened to a proposal: created, accepted or rejected.
MESSAGE_KIND
module-attribute
¶
MESSAGE_KIND: Final = 'artifactr.message.kind'
What a posted message is: a message, or a notice.
TURN_TRIGGER
module-attribute
¶
TURN_TRIGGER: Final = 'artifactr.turn.trigger'
What started a turn: message or resume.
TURN_OUTCOME
module-attribute
¶
TURN_OUTCOME: Final = 'artifactr.turn.outcome'
How a turn ended: completed, paused, stopped or failed.
RUN_STATUS
module-attribute
¶
RUN_STATUS: Final = 'artifactr.run.status'
How a run segment ended: completed, paused, stopped or failed.
RUN_REASON
module-attribute
¶
RUN_REASON: Final = 'artifactr.run.reason'
Why a run segment failed, when it has a typed reason, such as guardrail_blocked.
TOOL_STATUS
module-attribute
¶
TOOL_STATUS: Final = 'artifactr.tool.status'
How a tool call ended: ok, retry or error.
FEEDBACK_TYPE
module-attribute
¶
FEEDBACK_TYPE: Final = 'artifactr.feedback.type'
A feedback type's registered name.
FEEDBACK_TARGET
module-attribute
¶
FEEDBACK_TARGET: Final = 'artifactr.feedback.target'
What feedback is about: artifact, thread, turn or message.
CLOSE_CODE
module-attribute
¶
CLOSE_CODE: Final = 'artifactr.stream.close_code'
The WebSocket close code a thread-protocol connection ended with.
attribution
¶
attribution(
*,
tenant_id: TenantId,
workspace_id: WorkspaceId,
thread_id: ThreadId | None = None,
run_id: RunId | None = None,
actor: Actor | None = None,
user: Actor | None = None,
) -> dict[str, AttributeValue]
Return the attributes that attribute a span to its tenant, workspace, thread and actor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant_id
|
TenantId
|
The tenant. |
required |
workspace_id
|
WorkspaceId
|
The workspace. |
required |
thread_id
|
ThreadId | None
|
The thread, which is also the session. |
None
|
run_id
|
RunId | None
|
artifactr's run. |
None
|
actor
|
Actor | None
|
Who acts; its kind is recorded, and its id as the user when it is a person. |
None
|
user
|
Actor | None
|
The person the span acts for, when that is not |
None
|
Recording¶
These are what artifactr's components record with. Applications rarely need them, except annotate and attribution to attribute spans of their own.
Telemetry
¶
Telemetry(
*,
tracer_provider: TracerProvider | None = None,
meter_provider: MeterProvider | None = None,
)
The tracer and meter one artifactr component records with.
Workspaces, Runner and the surfaces each hold one, built from the providers they
are given, or the global ones. Metrics are recorded only through add and
record, which keep just the attributes the metric declares.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tracer_provider
|
TracerProvider | None
|
Where spans go. Defaults to the global tracer provider. |
None
|
meter_provider
|
MeterProvider | None
|
Where metrics go. Defaults to the global meter provider. |
None
|
attribution
¶
attribution(
*,
tenant_id: TenantId,
workspace_id: WorkspaceId,
thread_id: ThreadId | None = None,
run_id: RunId | None = None,
actor: Actor | None = None,
user: Actor | None = None,
) -> dict[str, AttributeValue]
Return the attributes that attribute a span to its tenant, workspace, thread and actor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant_id
|
TenantId
|
The tenant. |
required |
workspace_id
|
WorkspaceId
|
The workspace. |
required |
thread_id
|
ThreadId | None
|
The thread, which is also the session. |
None
|
run_id
|
RunId | None
|
artifactr's run. |
None
|
actor
|
Actor | None
|
Who acts; its kind is recorded, and its id as the user when it is a person. |
None
|
user
|
Actor | None
|
The person the span acts for, when that is not |
None
|
annotate
¶
annotate(
attributes: Attributes, span: Span | None = None
) -> None
Set attributes on a span (the current one by default), skipping None values.
current_trace_id
¶
current_trace_id() -> TraceId | None
Return the id of the current trace, or None when nothing is being traced.
current_traceparent
¶
current_traceparent() -> str | None
Return the W3C trace context of the current span, if there is one.
record_events
¶
record_events(
telemetry: Telemetry,
events: Iterable[KnownEvent],
*,
actor: Actor,
scope: Attributes,
) -> None
Count what a batch of committed events did.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
telemetry
|
Telemetry
|
Where to record. |
required |
events
|
Iterable[KnownEvent]
|
The events one command or fact appended. |
required |
actor
|
Actor
|
Who committed them. |
required |
scope
|
Attributes
|
The tenancy attributes (tenant and workspace). |
required |
command_attributes
¶
command_attributes(
command: Command,
*,
tenant_id: TenantId,
workspace_id: WorkspaceId,
actor: Actor,
) -> dict[str, AttributeValue]
Return the attributes of a command's span: its type, what it is about, and who sent it.
They carry ids, kinds and sizes, never content.
outcome_attributes
¶
Return the attributes that record what a command did.