From d5cae2536b02f2a713e45d9debd25426d81abf00 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:59:06 +0000 Subject: [PATCH] docs(agents): add extraction span manual instrumentation Document the gen_ai.extract span in the JavaScript and Python agent tracing manual instrumentation guides, following getsentry/sentry-conventions#728. Refs AGT-3212 Co-Authored-By: Arthur Knaus <7033940+ArthurKnaus@users.noreply.github.com> --- .../agent-tracing/manual-instrumentation.mdx | 75 +++++++++++++++++++ .../agent-tracing/manual-instrumentation.mdx | 65 ++++++++++++++++ .../ai-agents-module/extraction-span.mdx | 34 +++++++++ 3 files changed, 174 insertions(+) create mode 100644 includes/tracing/ai-agents-module/extraction-span.mdx diff --git a/docs/platforms/javascript/common/agent-tracing/manual-instrumentation.mdx b/docs/platforms/javascript/common/agent-tracing/manual-instrumentation.mdx index adb9aa7222349a..d5ebf0fbe5423e 100644 --- a/docs/platforms/javascript/common/agent-tracing/manual-instrumentation.mdx +++ b/docs/platforms/javascript/common/agent-tracing/manual-instrumentation.mdx @@ -383,6 +383,81 @@ If you call Jev through the +## Extraction Span + +This span represents an extraction of typed fields from an input, such as pulling the invoice number, due date, and total out of an invoice. Extraction models like Cloudflare Clairic produce these spans. They return typed values instead of a chat reply, so they get their own span type. + +Sentry identifies an extraction by `gen_ai.operation.name: "extract"`. + +```javascript +const extractionRequest = { + state: + "Invoice INV-2291 from Northwind Supply, issued 2026-09-02, net 30. 4 pallets at $250.00 each, shipping $80.00. Contact: Dana Ruiz, Priya Shah.", + fields: { + invoice_number: { type: "text" }, + due_date: { + type: "date", + instructions: "Issue date plus the net payment days", + }, + total: { + type: "number", + decimals: 2, + unit: "USD", + instructions: "Amount due including shipping", + }, + contacts: { type: "list", items: "text" }, + }, +}; + +await Sentry.startSpan( + { + op: "gen_ai.extract", + name: "extract @cf/cloudflare/clairic", + attributes: { + "gen_ai.operation.name": "extract", + "gen_ai.request.model": "@cf/cloudflare/clairic", + "gen_ai.provider.name": "cloudflare.workers_ai", + "gen_ai.agent.name": "Billing Agent", // when this extraction runs under an agent + // The request body as you send it, without `model` + "gen_ai.input.messages": JSON.stringify([extractionRequest]), + }, + }, + async (span) => { + // Call your extraction model; map its response into span attributes below + const { model, usage, ...response } = await yourExtractor.extract({ + model: "@cf/cloudflare/clairic", + ...extractionRequest, + }); + + span.setAttributes({ + "gen_ai.response.model": model, // e.g. "clairic" + // The response body as the provider returned it, without `model` and `usage`: + // { answers: { total: { type: "number", value: 1080, confidence: 0.9999 }, ... }, truncated: false } + "gen_ai.output.messages": JSON.stringify([response]), + "gen_ai.usage.input_tokens": usage.input_tokens, + "gen_ai.usage.output_tokens": usage.output_tokens, + }); + return response; + } +); +``` + +The input is a single object holding the request body: `state` is the text the model reads, and each entry in `fields` describes one field to extract. The output is a single object holding the response body, with `answers` keyed by those same field names. Record both as you sent and received them, without changing the values. If the request includes images, replace each one with the string `"[Blob substitute]"` instead of recording the image data. See the attributes below for the full shape. + +Text and list answers include `[start, end]` character offsets into `state`. Sentry uses them to highlight each extracted value in the source text, so keep them as the provider returned them. + +Pass the raw provider model string in `gen_ai.request.model` (and `gen_ai.response.model`) so Sentry can resolve the model icon and any [model costs](/product/agents/costs/). + + + +If you call Clairic through the [Cloudflare Workers AI binding](/platforms/javascript/guides/cloudflare/agent-tracing/workers-ai/) with `@sentry/cloudflare` 11.10.0 or later, these spans are created automatically, so you don't need to instrument them by hand. + + + + + + + ## Memory Span This span represents a read from, write to, or management operation against an agent's persistent memory store, such as retrieving relevant memories before a model call or saving new ones after it. diff --git a/docs/platforms/python/agent-tracing/manual-instrumentation.mdx b/docs/platforms/python/agent-tracing/manual-instrumentation.mdx index 898cad3d59d44e..1b146af41d7b06 100644 --- a/docs/platforms/python/agent-tracing/manual-instrumentation.mdx +++ b/docs/platforms/python/agent-tracing/manual-instrumentation.mdx @@ -349,6 +349,71 @@ If you call Jev through the TypeSafe +## Extraction Span + +This span represents an extraction of typed fields from an input, such as pulling the invoice number, due date, and total out of an invoice. Extraction models like Cloudflare Clairic produce these spans. They return typed values instead of a chat reply, so they get their own span type. + +Sentry identifies an extraction by `gen_ai.operation.name: "extract"`. + +```python +import json +import sentry_sdk + +extraction_request = { + "state": ( + "Invoice INV-2291 from Northwind Supply, issued 2026-09-02, net 30. " + "4 pallets at $250.00 each, shipping $80.00. Contact: Dana Ruiz, Priya Shah." + ), + "fields": { + "invoice_number": {"type": "text"}, + "due_date": { + "type": "date", + "instructions": "Issue date plus the net payment days", + }, + "total": { + "type": "number", + "decimals": 2, + "unit": "USD", + "instructions": "Amount due including shipping", + }, + "contacts": {"type": "list", "items": "text"}, + }, +} + +with sentry_sdk.start_span( + op="gen_ai.extract", + name="extract @cf/cloudflare/clairic", + attributes={ + "gen_ai.operation.name": "extract", + "gen_ai.request.model": "@cf/cloudflare/clairic", + "gen_ai.provider.name": "cloudflare.workers_ai", + "gen_ai.agent.name": "Billing Agent", # when this extraction runs under an agent + # The request body as you send it, without "model" + "gen_ai.input.messages": json.dumps([extraction_request]), + }, +) as span: + # Call your extraction model; map its response into span attributes below + result = your_extractor.extract(model="@cf/cloudflare/clairic", **extraction_request) + response = {k: v for k, v in result.items() if k not in ("model", "usage")} + + span.set_data("gen_ai.response.model", result["model"]) # e.g. "clairic" + # The response body as the provider returned it, without "model" and "usage": + # {"answers": {"total": {"type": "number", "value": 1080, "confidence": 0.9999}, ...}, "truncated": False} + span.set_data("gen_ai.output.messages", json.dumps([response])) + span.set_data("gen_ai.usage.input_tokens", result["usage"]["input_tokens"]) + span.set_data("gen_ai.usage.output_tokens", result["usage"]["output_tokens"]) +``` + +The input is a single object holding the request body: `state` is the text the model reads, and each entry in `fields` describes one field to extract. The output is a single object holding the response body, with `answers` keyed by those same field names. Record both as you sent and received them, without changing the values. If the request includes images, replace each one with the string `"[Blob substitute]"` instead of recording the image data. See the attributes below for the full shape. + +Text and list answers include `[start, end]` character offsets into `state`. Sentry uses them to highlight each extracted value in the source text, so keep them as the provider returned them. + +Pass the raw provider model string in `gen_ai.request.model` (and `gen_ai.response.model`) so Sentry can resolve the model icon and any [model costs](/product/agents/costs/). + + + + + ## Memory Span This span represents a read from, write to, or management operation against an agent's persistent memory store, such as retrieving relevant memories before a model call or saving new ones after it. diff --git a/includes/tracing/ai-agents-module/extraction-span.mdx b/includes/tracing/ai-agents-module/extraction-span.mdx new file mode 100644 index 00000000000000..70b67218bfd5fa --- /dev/null +++ b/includes/tracing/ai-agents-module/extraction-span.mdx @@ -0,0 +1,34 @@ +Describes an extraction of typed fields from an input, such as the total and due date of an invoice (for example a Cloudflare Clairic call). + +- The span `op` (transaction mode) or the span's `sentry.op` attribute (stream mode) MUST be `"gen_ai.extract"`. +- The span `name` SHOULD be `"extract {gen_ai.request.model}"`. (e.g. `"extract @cf/cloudflare/clairic"`) +- The `gen_ai.operation.name` attribute MUST be `"extract"`. +- The `gen_ai.request.model` attribute SHOULD be the requested extraction model. (e.g. `"@cf/cloudflare/clairic"`) +- The `gen_ai.response.model` attribute SHOULD be the concrete model that responded. Omit it when the provider doesn't report one. (e.g. `"clairic"`) +- The `gen_ai.provider.name` attribute SHOULD be set to the provider. (e.g. `"cloudflare.workers_ai"`) +- The `gen_ai.agent.name` attribute SHOULD be set to the agent's name when the extraction runs under an agent. (e.g. `"Billing Agent"`) + +Additional attributes on the span: + +| Data Attribute | Type | Requirement Level | Description | Example | +| :--------------------------- | :----- | :---------------- | :------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------ | +| `gen_ai.input.messages` | string | optional | A single object holding the request body, without `model` (stringified JSON). **[0]** | `'[{"state": "Invoice INV-2291 ...", "fields": {"total": {"type": "number", "unit": "USD"}}}]'` | +| `gen_ai.output.messages` | string | optional | A single object holding the response body, without `model` and `usage` (stringified JSON). **[1]** | `'[{"answers": {"total": {"type": "number", "value": 1080, "confidence": 0.99}}, "truncated": false}]'` | +| `gen_ai.usage.input_tokens` | int | optional | The number of input tokens used by the extraction, including all passes. | `655` | +| `gen_ai.usage.output_tokens` | int | optional | The number of output tokens used by the extraction. | `0` | + +Don't send the other usage counts that the provider reports (such as Clairic's `state_tokens`, `image_tokens`, `schema_tokens`, and `passes`). `gen_ai.usage.input_tokens` already includes them. + +**[0]** The input is the request body as the caller passed it. Don't change the values, and leave out the keys the caller didn't set: + +| Field | Type | Description | +| :--------------- | :----------- | :-------------------------------------------------------------------------------------------------- | +| `state` | string | The text the model reads. Required when there are no `images`. Text answers point into this string. | +| `fields` | object | Required. The field specs, keyed by field name. | +| `images` | string array | One `"[Blob substitute]"` string for each image. Never send the image data. | +| `reference_date` | string | The ISO date that anchors dates written without a year. | +| `options` | object | Request options, such as `include_quantiles` or `return_state_text`. | + +Each field spec has a `type`: `text`, `number`, `date`, `time`, `datetime`, `duration`, `list`, `estimate`, or `forecast`. It can also carry `instructions` and type-specific settings, such as `unit`, `integer`, and `decimals` for numbers, `items` for lists (`text`, `number`, or `date`), and `min`, `max`, and `step` for estimates and forecasts. + +**[1]** The output is the response body as the provider returned it. Don't change the values. `answers` has one answer for each field in the input, keyed by field name, and `truncated` is copied as returned. Each answer has the field's `type` and a `value`, which is `null` when the input doesn't determine it. Answers can also carry `guess`, `p_null`, and `confidence`. `text` answers carry a `span` and `list` answers carry `spans`: `[start, end]` character offsets into `state` (or into `state_text`, if the provider returns it), where `end` is exclusive. `estimate` answers carry `mean`, `median`, `p10`, and `p90`, and `forecast` answers carry one estimate per step in `steps`.