You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Only use attributes from [Sentry Gen AI Conventions](https://getsentry.github.io/sentry-conventions/attributes/gen_ai/).
40
-
41
-
## Streaming
42
-
43
-
-**Non-streaming:**`startSpan()`, set attributes from response
44
-
-**Streaming:**`startSpanManual()`, accumulate state via async generator or event listeners, set `GEN_AI_RESPONSE_STREAMING_ATTRIBUTE: true`, call `span.end()` in finally block
Span ops and attributes are specified outside this repo. Never invent or hardcode either:
47
12
48
-
## Token Accumulation
13
+
-[gen_ai attributes](https://getsentry.github.io/sentry-conventions/attributes/gen_ai/) and [gen_ai ops](https://getsentry.github.io/sentry-conventions/ops/#gen_ai) — normative; import from `@sentry/conventions/attributes` and `@sentry/conventions/op`
14
+
-[RFC 0153](https://github.com/getsentry/rfcs/blob/main/text/0153-decoupling-sentrys-generative-ai-conventions-from-open-telemetry.md) — why Sentry's gen-AI conventions diverge from the [OTel gen-ai semconv](https://opentelemetry.io/docs/specs/semconv/gen-ai/)
49
15
50
-
-**Child spans:** Set tokens directly from API response via `setTokenUsageAttributes()`
51
-
-**Parent spans (`invoke_agent`):** Accumulate from children using event processor (see `vercel-ai/`)
16
+
Derive the op with `getGenAiSpanOp()` from `ai/core/utils.ts` rather than picking one by hand. `ai/core/gen-ai-attributes.ts` is for gap-fillers only — keys with no `@sentry/conventions` equivalent — so check it last, not first.
2.**Node.js:** Add `callWhenPatched()` optimization in `packages/node/src/integrations/tracing/{provider}/index.ts` — defers registration until package is imported
59
-
3.**Edge:** Direct registration in `packages/cloudflare/src/integrations/tracing/{provider}.ts` — no OTel, call processors immediately
| 1 — Native tracing channel | the SDK publishes to `diagnostics_channel`|`integrations/vercel-ai/vercel-ai-dc-subscriber.ts`|
31
+
| 2 — Orchestrion channels | the SDK has no telemetry of its own |`integrations/openai.ts` + `orchestrion/config/openai.ts`|
32
+
| 3 — Callback/exporter | the SDK exposes hooks or an exporter |`ai/langchain/`, `ai/mastra/` (exporter-shaped agent framework) |
81
33
82
-
## Auto-Instrumentation (Node.js)
34
+
What the reference files won't tell you:
83
35
84
-
**Mandatory** for Node.js AI integrations. OTel only patches when the package is imported (zero cost if unused).
36
+
- A provider can need two patterns at once: `vercelAIIntegration` subscribes to native `ai:telemetry` for `ai` >= 7 _and_ runs orchestrion injection for v4-v6.
37
+
- Pattern 1 subscribers are safe to register unconditionally — subscribing is a no-op on SDK versions that never publish.
85
38
86
-
### Steps
39
+
##Where The Code Goes
87
40
88
-
1.**Add to `getAutoPerformanceIntegrations()`** in `packages/node/src/integrations/tracing/index.ts` — LangChain MUST come first
89
-
2.**Add to `getOpenTelemetryInstrumentationToPreload()`** for OTel-based integrations
90
-
3.**Export from `packages/node/src/index.ts`**: integration function + options type
- Runtime packages (`node`, `cloudflare`, `bun`, ...) re-export from `@sentry/server-utils` — they never define their own
44
+
- Exception: Workers AI is client-wrapped in `packages/cloudflare/src/instrumentations/worker/instrumentEnv.ts`
95
45
96
-
## Key Rules
46
+
## Gotchas
97
47
98
-
1. Respect `dataCollection.genAI` for recording input and output messages
99
-
2. Set `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'auto.ai.{provider}'` (alphanumerics, `_`, `.` only)
100
-
3. Truncate large data with helper functions from `utils.ts`
101
-
4.`gen_ai.invoke_agent` for parent ops, `gen_ai.chat` for child ops
48
+
1.**Detect streaming from the result shape** — an async-iterable or the SDK's stream object — not from `params.stream`. Only the manual `instrument{Provider}Client()` API keys off `params.stream === true`.
49
+
2.**Never set streamed response attributes by hand.** Accumulate into a `StreamResponseState` and call `endStreamSpan()` (`ai/openai/streaming.ts` for an async generator, `ai/anthropic-ai/streaming.ts` for a listener-based stream).
50
+
3.**Never truncate message payloads.** Truncation was removed in v11 (#23045) and nothing downstream caps them; size limiting is server-side.
51
+
4.**Never roll child token usage up onto parent spans.** Tree totals are computed product-side from the full span tree.
52
+
5.**Never read `dataCollection.genAI` directly.** Gate input/output recording on `resolveAIRecordingOptions()`.
53
+
6.**LangChain must be registered first** in `getTracingIntegrations()`, so it can disable the provider integrations before they instrument.
54
+
7. Set `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'auto.ai.{provider}'` (alphanumerics, `_`, `.` only).
102
55
103
56
## Checklist
104
57
105
-
-[ ] Runtime-specific code placed only in that runtime's package
106
-
-[ ] Added to `getAutoPerformanceIntegrations()` in correct order (Node.js)
107
-
-[ ] Added to `getOpenTelemetryInstrumentationToPreload()` (Node.js with OTel)
108
-
-[ ] Exported from appropriate package index
109
-
-[ ] E2E tests added and verifying auto-instrumentation
110
-
-[ ] Only used attributes from [Sentry Gen AI Conventions](https://getsentry.github.io/sentry-conventions/attributes/gen_ai/)
111
-
-[ ] JSDoc says "enabled by default" or "not enabled by default"
112
-
-[ ] Documented how to disable (if auto-enabled)
113
-
-[ ] Verified OTel only patches when package imported (Node.js)
-[ ] Instrumentation in `src/ai/`, integration in `src/integrations/`, registered in `getTracingIntegrations()` (LangChain first)
59
+
-[ ] Exported from `packages/server-utils/src/index.ts`, re-exported from the supported runtime packages
60
+
-[ ] E2E tests in `dev-packages/node-integration-tests/suites/tracing/{provider}/` (and `cloudflare-integration-tests/` if supported)
61
+
-[ ] Ops and attributes from `@sentry/conventions`, op derived via `getGenAiSpanOp()`
62
+
-[ ] Recording gated on `resolveAIRecordingOptions()`; no truncation, no token rollup
63
+
-[ ] JSDoc names the channels subscribed to, the supported SDK versions, and — for Pattern 2 — that it requires the Sentry runtime hook or bundler plugin
64
+
-[ ] Patching happens only once the target package is imported (zero cost if unused)
120
65
121
66
**When in doubt, follow the pattern of the most similar existing integration.**
-`consoleSandbox(() => { console.warn(...) })` for intentional user-facing warnings (e.g. init-time misconfiguration messages). The `consoleSandbox` wrapper prevents the SDK's own console instrumentation from intercepting the call. Bare `console.*` calls outside very early init paths (e.g. before the logger is available) should be flagged.
69
69
- Flag `url.full`, `url.query`, `http.target` or `request.query_string` being set from a URL that isn't filtered. Wrap the value in `filterCollectedUrl()` (or `filterCollectedUrlQuery()` for a bare query string), passing the `client` if one is in scope, so `dataCollection.urlQueryParams` applies. Values that can't contain a query (a bare pathname, a queue URL) are fine. The `sdk/no-unfiltered-url-attributes` lint rule catches direct attribute writes, so look for what it can't: URLs passed through a helper or variable first, deprecated aliases set next to a filtered attribute, and URLs on breadcrumbs or events instead of spans.
70
70
- Flag span names built from a raw URL. Names follow `METHOD scheme://host/path` and must never contain a query string, so they need `stripUrlQueryAndFragment()`, not `filterCollectedUrl()`.
71
+
- Flag a SQL statement that reaches telemetry unsanitized. `db.query.text`, `db.query.summary`, a DB span name, and a breadcrumb carrying a statement all have to come from `sanitizeSqlQuery()` or `sanitizeSqlQueryWithSummary()` (`@sentry/server-utils`). Inline literals are user data, and OTel allows collecting query text only once they are replaced with `?`. This is deliberately not gated on `dataCollection.databaseQueryData`, which does not cover query text.
72
+
- Cover every place the statement lands, not only the span attribute. The two that get forgotten are the breadcrumb beside the span and the span name used when span streaming is off.
73
+
- Sanitize each statement of a batch before joining them.
74
+
- Pass the dialect. `toSqlDialect()` maps a driver or `db.system.name` value to one, and a missing dialect leaves MySQL and SQL Server values in the statement.
75
+
- Leave Redis command text alone. It is not SQL and has its own redaction path.
71
76
- Flag usage of the following APIs: `getCurrentScope()`, `getIsolationScope()`, `getClient()` if they are avoidable. Flag it with severity Low and acknowledge from the start that this is more a "is this necessary" check, rather than a rule violation.
72
77
- Reason for flagging: Usage of these APIs is problematic for multi-client setups where either there is no "current" client/scope, or the wrong client might be used. Calling these APIs would create a current scope, thereby misleading any future calls to these APIs.
73
78
- What to do instead: Use an existing reference to the scope or client. For example, this is possible in most `Integration` hooks.
Copy file name to clipboardExpand all lines: AGENTS.md
+9-8Lines changed: 9 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,16 +64,17 @@ Uses **Git Flow** (see `docs/gitflow.md`).
64
64
-**Omit the "Summary" heading** in PR bodies — lead with the summary text itself, no `## Summary` header.
65
65
- Include `Fixes #<issue-number>` somewhere in the PR body so the merge auto-closes the linked issue.
66
66
- Always open PRs as draft.
67
-
-Include reasoning of changes in the PR description, as well as decisions that were taken during implementation. Do not explain the implementation that can be viewed in the code.
67
+
-**Keep PR descriptions condensed** — usually a few sentences. Explain the _reasoning_: why the change is needed, and any non-obvious decisions or tradeoffs made along the way. Do **not** walk through the implementation or describe what changed file-by-file — the diff already shows that. Only go longer when the decisions genuinely warrant it.
68
68
69
69
## Architecture
70
70
71
-
-`packages/types/` is **deprecated — never modify it**. Types live in
72
-
`packages/core/`.
73
-
- An AI provider integration spans three places: core instrumentation in
74
-
`packages/core/src/tracing/{provider}/`, the Node integration in
75
-
`packages/node/src/integrations/tracing/{provider}/`, and the edge
76
-
runtime in `packages/cloudflare/src/integrations/tracing/{provider}.ts`.
71
+
- Types live in `packages/core/`. The `@sentry/types` package is gone.
72
+
- An AI provider integration spans two places, both in
73
+
`packages/server-utils/`: the gen-AI instrumentation logic in
74
+
`src/ai/{provider}/`, and the integration that wires it up in
75
+
`src/integrations/{provider}.ts`, registered in `getTracingIntegrations()`.
76
+
Runtime packages (`node`, `cloudflare`, ...) re-export from
77
+
`@sentry/server-utils` rather than defining their own.
77
78
78
79
## Linting & Formatting
79
80
@@ -90,7 +91,7 @@ Uses **Git Flow** (see `docs/gitflow.md`).
90
91
- Only use libraries already in the codebase
91
92
- Never expose secrets or keys
92
93
- When modifying files, cover all occurrences (including `src/` and `test/`)
93
-
- Comments explain **why**, never **what** — never add a comment that restates what the code does or describes the change being made; only comment when the reasoning isn't obvious from the code itself
94
+
-**Write few comments; default to none.**Comments explain **why**, never **what** — never add a comment that restates what the code does or narrates the change being made. Only add one when the reasoning isn't clear from the code itself, or to flag a tradeoff or something that would otherwise look surprising to a reader. When in doubt, leave it out.
94
95
- Do not use `expect(someSpy.mock.calls[0]?.[0])` or similar constructs to check what a spy was called with.
95
96
Instead use `expect(someSpy).toHaveBeenCalledWith(...)` or derivatives for a more readable and less brittle test assertion.
0 commit comments