Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
{
"log": {
"_recordingName": "Test Anthropic instrumentation/should set attributes in span for beta messages with thinking",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "6.0.6"
},
"entries": [
{
"_id": "36fcbb1741e0f2a632c89d8c928a5d53",
"_order": 0,
"cache": {},
"request": {
"bodySize": 192,
"cookies": [],
"headers": [
{
"name": "accept",
"value": "application/json"
},
{
"name": "anthropic-beta",
"value": "interleaved-thinking-2025-05-14"
},
{
"name": "anthropic-version",
"value": "2023-06-01"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "user-agent",
"value": "Anthropic/JS 0.56.0"
},
{
"name": "x-stainless-arch",
"value": "arm64"
},
{
"name": "x-stainless-lang",
"value": "js"
},
{
"name": "x-stainless-os",
"value": "MacOS"
},
{
"name": "x-stainless-package-version",
"value": "0.56.0"
},
{
"name": "x-stainless-retry-count",
"value": "0"
},
{
"name": "x-stainless-runtime",
"value": "node"
},
{
"name": "x-stainless-runtime-version",
"value": "v20.11.1"
},
{
"name": "x-stainless-timeout",
"value": "600"
}
],
"headersSize": 584,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\"max_tokens\":2048,\"messages\":[{\"role\":\"user\",\"content\":\"What is 2+2? Think through this step by step.\"}],\"model\":\"claude-opus-4-1-20250805\",\"thinking\":{\"type\":\"enabled\",\"budget_tokens\":1024}}"
},
"queryString": [
{
"name": "beta",
"value": "true"
}
],
"url": "https://api.anthropic.com/v1/messages?beta=true"
},
"response": {
"bodySize": 1570,
"content": {
"mimeType": "application/json",
"size": 1570,
"text": "{\"id\":\"msg_018V3xGyrq6nc25GVuWiaKHx\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-opus-4-1-20250805\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"This is a very simple arithmetic question. The user is asking for 2+2, which equals 4. They've asked me to think through it step by step, so I should show the basic addition process even though it's elementary.\",\"signature\":\"EvsCCkYIBhgCKkDcMyQ9Uh8CsGT5WmyeuwbI5yYSB1cbUyx5DC/zqmUMb0n5Zyi+Oz/fXpxGLJUmfnKp3zXuuOhybxupMRhBmK+3EgxVj1F8BGfmpqpOxjcaDIbIp9dBQHkej5KsTyIwJDTMjuH/q/vu4Pk/Zf4w9htqsZOPLfYdg/EbXIdeBNV4sJ6Jtiu+KzBg4O5fTgjPKuIBuD8ob8cR9xna6cV8JHxfUT9IeX3huQ2oF/vJC/99vqn4F//OEjiN8kCKPlJo28+S72odghUyF8TUITL/UIBWZ3kcQtwCdmytlB1+2Bld5osVVmOi4KApBkl9cRTOemDzkJHBFmhJ1AuUyZ2Fl2hVGmE2ACE8CPYU+iCZpZX2l4tWCT2M1wCaNTwNSqHcQtC/C0H9geP6Vyc2K2P6TcUIuUv8CFVIdqwcYDnbREhlY2Jv7nmaVDSraCvCXWj3Y/sQulDsBOqp6drQAITcWPJI8wbDmw8fIEhcCyujlcpanKzBhhgB\"},{\"type\":\"text\",\"text\":\"I'll work through this simple addition step by step.\\n\\n**Step 1:** Identify what we're adding\\n- We have two numbers: 2 and 2\\n- We need to add them together\\n\\n**Step 2:** Perform the addition\\n- Start with the first number: 2\\n- Add the second number: + 2\\n- When we combine 2 items with 2 more items, we get 4 items total\\n\\n**Step 3:** State the result\\n- 2 + 2 = 4\\n\\nThe answer is **4**.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":49,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":186,\"service_tier\":\"standard\"}}"
},
"cookies": [],
"headers": [
{
"name": "anthropic-organization-id",
"value": "617d109c-a187-4902-889d-689223d134aa"
},
{
"name": "anthropic-ratelimit-input-tokens-limit",
"value": "2000000"
},
{
"name": "anthropic-ratelimit-input-tokens-remaining",
"value": "2000000"
},
{
"name": "anthropic-ratelimit-input-tokens-reset",
"value": "2025-08-21T11:41:58Z"
},
{
"name": "anthropic-ratelimit-output-tokens-limit",
"value": "400000"
},
{
"name": "anthropic-ratelimit-output-tokens-remaining",
"value": "400000"
},
{
"name": "anthropic-ratelimit-output-tokens-reset",
"value": "2025-08-21T11:42:02Z"
},
{
"name": "anthropic-ratelimit-requests-limit",
"value": "4000"
},
{
"name": "anthropic-ratelimit-requests-remaining",
"value": "3999"
},
{
"name": "anthropic-ratelimit-requests-reset",
"value": "2025-08-21T11:41:57Z"
},
{
"name": "anthropic-ratelimit-tokens-limit",
"value": "2400000"
},
{
"name": "anthropic-ratelimit-tokens-remaining",
"value": "2400000"
},
{
"name": "anthropic-ratelimit-tokens-reset",
"value": "2025-08-21T11:41:58Z"
},
{
"name": "cf-cache-status",
"value": "DYNAMIC"
},
{
"name": "cf-ray",
"value": "9729dd411cdd6756-ATL"
},
{
"name": "connection",
"value": "keep-alive"
},
{
"name": "content-encoding",
"value": "gzip"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "date",
"value": "Thu, 21 Aug 2025 11:42:02 GMT"
},
{
"name": "request-id",
"value": "req_011CSLo11ceKMKF1kTBWoKxZ"
},
{
"name": "server",
"value": "cloudflare"
},
{
"name": "strict-transport-security",
"value": "max-age=31536000; includeSubDomains; preload"
},
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "via",
"value": "1.1 google"
},
{
"name": "x-envoy-upstream-service-time",
"value": "5555"
},
{
"name": "x-robots-tag",
"value": "none"
}
],
"headersSize": 1098,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2025-08-21T11:41:56.089Z",
"time": 6594,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 6594
}
}
],
"pages": [],
"version": "1.2"
}
}
21 changes: 20 additions & 1 deletion packages/instrumentation-anthropic/src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
CONTEXT_KEY_ALLOW_TRACE_CONTENT,
SpanAttributes,
} from "@traceloop/ai-semantic-conventions";
import { AnthropicInstrumentationConfig } from "./types";
import { AnthropicInstrumentationConfig, MessageCreateParamsWithThinking } from "./types";
import { version } from "../package.json";
import type * as anthropic from "@anthropic-ai/sdk";
import type {
Expand Down Expand Up @@ -72,6 +72,11 @@ export class AnthropicInstrumentation extends InstrumentationBase {
"create",
this.patchAnthropic("chat", module),
);
this._wrap(
module.Anthropic.Beta.Messages.prototype,
"create",
this.patchAnthropic("chat", module),
);
Comment on lines +76 to +80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard Beta.Messages patching for SDK versions without Beta API.

Accessing module.Anthropic.Beta.Messages.prototype unconditionally can throw on older @anthropic-ai/sdk versions. Add a structural guard to avoid runtime errors and log that Beta patching is skipped.

Apply this diff:

-    this._wrap(
-      module.Anthropic.Beta.Messages.prototype,
-      "create",
-      this.patchAnthropic("chat", module),
-    );
+    if (module?.Anthropic?.Beta?.Messages?.prototype?.create) {
+      this._wrap(
+        module.Anthropic.Beta.Messages.prototype,
+        "create",
+        this.patchAnthropic("chat", module),
+      );
+    } else {
+      this._diag.debug(
+        "Anthropic Beta.Messages.create not found; skipping manual beta patch"
+      );
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this._wrap(
module.Anthropic.Beta.Messages.prototype,
"create",
this.patchAnthropic("chat", module),
);
// Guard Beta.Messages patching for SDK versions without Beta API.
if (module?.Anthropic?.Beta?.Messages?.prototype?.create) {
this._wrap(
module.Anthropic.Beta.Messages.prototype,
"create",
this.patchAnthropic("chat", module),
);
} else {
this._diag.debug(
"Anthropic Beta.Messages.create not found; skipping manual beta patch"
);
}
🤖 Prompt for AI Agents
In packages/instrumentation-anthropic/src/instrumentation.ts around lines 75 to
79, the code unconditionally accesses module.Anthropic.Beta.Messages.prototype
which throws on older @anthropic-ai/sdk; modify this section to check that
module.Anthropic, module.Anthropic.Beta, and module.Anthropic.Beta.Messages (and
its prototype) exist before calling this._wrap, and if any are missing call the
logger to record that Beta patching was skipped so runtime errors are avoided.

}

protected init(): InstrumentationModuleDefinition {
Expand All @@ -97,6 +102,11 @@ export class AnthropicInstrumentation extends InstrumentationBase {
"create",
this.patchAnthropic("chat", moduleExports),
);
this._wrap(
moduleExports.Anthropic.Beta.Messages.prototype,
"create",
this.patchAnthropic("chat", moduleExports),
);
Comment on lines +106 to +110
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Same guard needed in automatic patch() path.

Mirror the safety check here to prevent TypeError when the installed SDK lacks the Beta API.

-    this._wrap(
-      moduleExports.Anthropic.Beta.Messages.prototype,
-      "create",
-      this.patchAnthropic("chat", moduleExports),
-    );
+    if (moduleExports?.Anthropic?.Beta?.Messages?.prototype?.create) {
+      this._wrap(
+        moduleExports.Anthropic.Beta.Messages.prototype,
+        "create",
+        this.patchAnthropic("chat", moduleExports),
+      );
+    } else {
+      this._diag.debug(
+        "Anthropic Beta.Messages.create not found; skipping beta patch"
+      );
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this._wrap(
moduleExports.Anthropic.Beta.Messages.prototype,
"create",
this.patchAnthropic("chat", moduleExports),
);
if (moduleExports?.Anthropic?.Beta?.Messages?.prototype?.create) {
this._wrap(
moduleExports.Anthropic.Beta.Messages.prototype,
"create",
this.patchAnthropic("chat", moduleExports),
);
} else {
this._diag.debug(
"Anthropic Beta.Messages.create not found; skipping beta patch"
);
}
🤖 Prompt for AI Agents
In packages/instrumentation-anthropic/src/instrumentation.ts around lines 105 to
109, the automatic patch path unconditionally attempts to wrap
moduleExports.Anthropic.Beta.Messages.prototype.create which throws a TypeError
when the installed SDK lacks the Beta API; add the same safety guard used
elsewhere to first verify moduleExports.Anthropic, moduleExports.Anthropic.Beta
and moduleExports.Anthropic.Beta.Messages and that prototype.create exists
before calling this._wrap, and return/skip the wrap when any of those are
missing so the patch is safe for SDKs without Beta.

return moduleExports;
}

Expand All @@ -108,6 +118,7 @@ export class AnthropicInstrumentation extends InstrumentationBase {

this._unwrap(moduleExports.Anthropic.Completions.prototype, "create");
this._unwrap(moduleExports.Anthropic.Messages.prototype, "create");
this._unwrap(moduleExports.Anthropic.Beta.Messages.prototype, "create");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard unwrap to match conditional wrapping.

Unwrapping an undefined prototype will also throw. Protect the unwrap with the same shape check.

-    this._unwrap(moduleExports.Anthropic.Beta.Messages.prototype, "create");
+    if (moduleExports?.Anthropic?.Beta?.Messages?.prototype) {
+      this._unwrap(moduleExports.Anthropic.Beta.Messages.prototype, "create");
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this._unwrap(moduleExports.Anthropic.Beta.Messages.prototype, "create");
if (moduleExports?.Anthropic?.Beta?.Messages?.prototype) {
this._unwrap(moduleExports.Anthropic.Beta.Messages.prototype, "create");
}
🤖 Prompt for AI Agents
In packages/instrumentation-anthropic/src/instrumentation.ts around line 121,
the call this._unwrap(moduleExports.Anthropic.Beta.Messages.prototype, "create")
can throw if the prototype is undefined; guard the unwrap with the same
conditional shape check used when wrapping (check that moduleExports.Anthropic,
Beta, Messages and Messages.prototype all exist) and only call this._unwrap when
that prototype is present so unwrapping mirrors the conditional wrapping logic.

}

private patchAnthropic(
Expand Down Expand Up @@ -202,6 +213,14 @@ export class AnthropicInstrumentation extends InstrumentationBase {
attributes[SpanAttributes.LLM_REQUEST_TOP_P] = params.top_p;
attributes[SpanAttributes.LLM_TOP_K] = params.top_k;

// Handle thinking parameters
const paramsWithThinking = params as MessageCreateParamsWithThinking;
if (paramsWithThinking.thinking) {
const thinking = paramsWithThinking.thinking;
attributes["llm.request.thinking.type"] = thinking.type;
attributes["llm.request.thinking.budget_tokens"] = thinking.budget_tokens;
}

if (type === "completion") {
attributes[SpanAttributes.LLM_REQUEST_MAX_TOKENS] =
params.max_tokens_to_sample;
Expand Down
11 changes: 10 additions & 1 deletion packages/instrumentation-anthropic/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ export interface AnthropicInstrumentationConfig extends InstrumentationConfig {
traceContent?: boolean;

/**
* A custom logger to log any exceptions that happen during span creation.
* A custom logger to log any exceptions that happen during span created.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the comment: 'span created' appears to be a mistake. It might be intended to read 'span creation' or 'spans are created'.

Suggested change
* A custom logger to log any exceptions that happen during span created.
* A custom logger to log any exceptions that happen during span creation.

*/
exceptionLogger?: (e: Error) => void;
}

export interface ThinkingParams {
type: "enabled";
budget_tokens: number;
}

export interface MessageCreateParamsWithThinking {
thinking?: ThinkingParams;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider aligning these types with upstream Anthropic SDK to avoid drift.

If the repo already depends on the official SDK, prefer reusing its request types so new fields/variants (if Anthropic extends the shape) flow through automatically. If that’s not feasible, keeping this minimal structural type is fine.

Option A (preferred when SDK types include thinking):

// types.ts
import type { MessageCreateParams } from "@anthropic-ai/sdk/resources/messages";

// Narrow to just what we need for instrumentation:
export type MessageCreateParamsWithThinking = Pick<MessageCreateParams, "thinking">;

Option B (keep structural typing) — add a brief doc to clarify intent:

/**
 * Minimal shape of the Anthropic `thinking` request param used by instrumentation.
 * Keep in sync with the Anthropic SDK to avoid type drift.
 */
export interface ThinkingParams {
  type: "enabled";
  budget_tokens?: number;
}
export interface MessageCreateParamsWithThinking {
  thinking?: ThinkingParams;
}

To confirm the latest allowed values for thinking.type and whether budget_tokens is optional in the beta API, please run this query:


🌐 Web query:

What are the current valid fields and allowed values for the Anthropic Messages API `thinking` request parameter (including the beta `anthropic.beta.messages.create`), and is `budget_tokens` optional?

💡 Result:

Short answer

  • The Messages API's thinking object accepts:
    • type — enum: "enabled" or "disabled".
    • budget_tokens — integer (when thinking is enabled).
  • budget_tokens is not optional when you turn thinking on — you must supply a token budget (minimum 1,024). The budget must normally be less than your max_tokens, except when using interleaved-thinking (beta) in which case the thinking budget can exceed max_tokens. (docs.anthropic.com)

Details / authoritative points

  • Field: thinking (object) — optional overall (only include it when you want extended thinking). The object form and behavior are documented in the Messages API reference and the Extended Thinking guide. (docs.anthropic.com)
  • thinking.type — required inside the thinking object; allowed values: "enabled" or "disabled". When "enabled" the model will emit thinking content blocks (summarized for Claude 4 models). (docs.anthropic.com)
  • thinking.budget_tokens — integer token budget for the model’s internal reasoning:
    • Minimum: 1,024 tokens.
    • Normally must be set to a value less than your request's max_tokens.
    • With interleaved thinking (a beta mode that lets the model interleave tool calls and thinking), the budget may exceed max_tokens because the effective token window becomes the larger context window (see the interleaved-thinking beta header). (docs.anthropic.com)
  • Behavior across models / summary visibility: Claude 3.7 returns full thinking output; Claude 4 models return summarized thinking by default (billing is for full thinking tokens even when you see the summary). (docs.anthropic.com)
  • Beta SDK / messages.create: the beta messages.create path (sdk/ beta namespace or using the anthropic-beta header) supports the same thinking parameter; you enable beta features via the anthropic-beta header or via the SDK beta namespace. Example SDK beta usages show passing thinking in the create call. (docs.anthropic.com, icodebroker.com)

Practical examples

  • Enable thinking (required budget):
    • { "thinking": { "type": "enabled", "budget_tokens": 10000 } } — valid (10000 ≥ 1024, and you must ensure < max_tokens unless using interleaved thinking). (docs.anthropic.com)
  • Disable thinking:
    • { "thinking": { "type": "disabled" } } — no budget_tokens needed/used. (docs.anthropic.com)

If you want I can:

  • Show a minimal curl / JSON example for either the stable endpoint or the beta SDK call, or
  • Look up any model-specific constraints on max_tokens vs. thinking budgets for a particular Claude model you plan to use.

Citations:


Update ThinkingParams to match Anthropic’s current thinking spec

The Anthropic Messages API now supports two thinking.type values—"enabled" and "disabled"—and requires budget_tokens only when "enabled" (minimum 1,024). To keep your types in sync and prevent drift:

• File: packages/instrumentation-anthropic/src/types.ts
Lines: 16–23

Suggested refactor (recommended):

-export interface ThinkingParams {
-  type: "enabled";
-  budget_tokens: number;
-}
+/**  
+ * Corresponds to Anthropic's `thinking` parameter:  
+ * - `"enabled"` requires a `budget_tokens` ≥ 1,024.  
+ * - `"disabled"` omits `budget_tokens`.  
+ */
+export type ThinkingParams =
+  | { type: "enabled";   budget_tokens: number }
+  | { type: "disabled" };

-export interface MessageCreateParamsWithThinking {
-  thinking?: ThinkingParams;
-}
+export interface MessageCreateParamsWithThinking {
+  thinking?: ThinkingParams;
+}

Alternatively, if you depend on @anthropic-ai/sdk, you can reuse its types:

import type { MessageCreateParams } from "@anthropic-ai/sdk/resources/messages";

export type MessageCreateParamsWithThinking = Pick<
  MessageCreateParams,
  "thinking"
>;

— Ensure budget_tokens meets the minimum 1,024 token requirement when type is "enabled" and leave it out for "disabled".

🤖 Prompt for AI Agents
In packages/instrumentation-anthropic/src/types.ts around lines 16–23, update
the ThinkingParams/type definition so it accepts both "enabled" and "disabled"
for thinking.type and only requires budget_tokens when type is "enabled" (with a
minimum value of 1024); implement this by replacing the single-interface with a
discriminated union (one branch for { type: "enabled"; budget_tokens: number }
and one for { type: "disabled" }) or by importing and reusing the
MessageCreateParams typing from @anthropic-ai/sdk to pick the thinking field,
and ensure any runtime/validation code enforces budget_tokens >= 1024 when type
=== "enabled".

84 changes: 84 additions & 0 deletions packages/instrumentation-anthropic/test/instrumentation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,4 +328,88 @@ describe("Test Anthropic instrumentation", async function () {
"user",
);
}).timeout(30000);

it("should set attributes in span for beta messages with thinking", async () => {
const message = await anthropic.beta.messages.create({
max_tokens: 2048,
betas: ['interleaved-thinking-2025-05-14'],
messages: [
{ role: "user", content: "What is 2+2? Think through this step by step." },
],
model: "claude-opus-4-1-20250805",
thinking: {
type: "enabled",
budget_tokens: 1024,
},
});

const spans = memoryExporter.getFinishedSpans();
const chatSpan = spans.find((span) => span.name === "anthropic.chat");

assert.ok(message);
assert.ok(chatSpan);
assert.strictEqual(
chatSpan.attributes[`${SpanAttributes.LLM_REQUEST_MODEL}`],
"claude-opus-4-1-20250805",
);
assert.strictEqual(
chatSpan.attributes[`${SpanAttributes.LLM_RESPONSE_MODEL}`],
"claude-opus-4-1-20250805",
);
assert.strictEqual(
chatSpan.attributes[`${SpanAttributes.LLM_REQUEST_MAX_TOKENS}`],
2048,
);

// Check if thinking parameters are captured (these will fail initially)
assert.strictEqual(
chatSpan.attributes["llm.request.thinking.type"],
"enabled",
);
assert.strictEqual(
chatSpan.attributes["llm.request.thinking.budget_tokens"],
1024,
);

// Check prompts
assert.strictEqual(
chatSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.role`],
"user",
);
assert.strictEqual(
chatSpan.attributes[`${SpanAttributes.LLM_PROMPTS}.0.content`],
"What is 2+2? Think through this step by step.",
);

// Check that we capture both thinking and regular content blocks
const content = JSON.parse(chatSpan.attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`] as string);
assert.ok(Array.isArray(content));

interface ContentBlock {
type: string;
thinking?: string;
text?: string;
}

const thinkingBlock = content.find((block: ContentBlock) => block.type === "thinking");
const textBlock = content.find((block: ContentBlock) => block.type === "text");

assert.ok(thinkingBlock, "Should contain a thinking block");
assert.ok(thinkingBlock.thinking, "Thinking block should have thinking content");
assert.ok(textBlock, "Should contain a text block");
assert.ok(textBlock.text, "Text block should have text content");

// Verify token usage includes thinking tokens
assert.ok(
+chatSpan.attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`]! > 0,
);
assert.ok(
+chatSpan.attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`]! > 0,
);
assert.equal(
+chatSpan.attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`]! +
+chatSpan.attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`]!,
chatSpan.attributes[`${SpanAttributes.LLM_USAGE_TOTAL_TOKENS}`],
);
}).timeout(30000);
});
Loading