Skip to content

Conversation

koxudaxi
Copy link
Collaborator

No description provided.

@koxudaxi koxudaxi marked this pull request as ready for review July 25, 2025 09:12
@koxudaxi koxudaxi changed the title Add TS anthropic feat: add Anthropic instrumentation support for TS Jul 25, 2025
@codecov-commenter
Copy link

codecov-commenter commented Jul 25, 2025

Codecov Report

Attention: Patch coverage is 28.44165% with 1024 lines in your changes missing coverage. Please review.

Project coverage is 88.49%. Comparing base (600beac) to head (71f847f).

Files with missing lines Patch % Lines
...rc/instrumentors/anthropic-otel-instrumentation.ts 8.28% 1008 Missing ⚠️
sdks/typescript/src/wrap-anthropic.ts 97.11% 9 Missing ⚠️
sdks/typescript/src/register.ts 65.00% 7 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##           LILYPAD-209     #618      +/-   ##
===============================================
- Coverage        93.74%   88.49%   -5.25%     
===============================================
  Files              229      231       +2     
  Lines            16332    17757    +1425     
  Branches           713      763      +50     
===============================================
+ Hits             15310    15714     +404     
- Misses            1018     2039    +1021     
  Partials             4        4              
Flag Coverage Δ
App-ubuntu-latest-python3.10 99.87% <ø> (ø)
App-ubuntu-latest-python3.11 99.87% <ø> (ø)
App-ubuntu-latest-python3.12 99.87% <ø> (ø)
App-ubuntu-latest-python3.13 99.87% <ø> (ø)
SDK-ubuntu-latest-python3.10 100.00% <ø> (ø)
SDK-ubuntu-latest-python3.11 100.00% <ø> (ø)
SDK-ubuntu-latest-python3.12 100.00% <ø> (ø)
SDK-ubuntu-latest-python3.13 100.00% <ø> (ø)
TypeScript-SDK-ubuntu-latest-node18 73.37% <28.44%> (-10.32%) ⬇️
TypeScript-SDK-ubuntu-latest-node20 73.37% <28.44%> (-10.32%) ⬇️
TypeScript-SDK-ubuntu-latest-node22 73.37% <28.44%> (-10.32%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Collaborator

@teamdandelion teamdandelion left a comment

Choose a reason for hiding this comment

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

There's a lot of code duplication (seems like around 2-4 copies of span creation, message recording, stream chunk processing, etc). I think this will really be difficult to keep track of and maintain as time goes on. I suggest some significant cleanup of the implementation

AnthropicMessageChunk,
} from '../types/anthropic';

// Import GenAI semantic conventions
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit : unnecessary comment

suppressInternalInstrumentation?: boolean;
}

// Symbol to mark instrumented modules
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: probably unnecessary?

}

private _applyPatch(moduleExports: any): any {
// Check if already instrumented by Lilypad
Copy link
Collaborator

Choose a reason for hiding this comment

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

More vibe coded comments :P


// Start span using the OTel way with active context
const span = tracer.startSpan(
`anthropic.messages ${params?.model || 'unknown'}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: wonder if it make sense to make the unknown string like unknown-model so it's a little clearer what is unknown?

Comment on lines +219 to +233
{
kind: SpanKind.CLIENT,
attributes: {
[SEMATTRS_GEN_AI_SYSTEM]: 'anthropic',
[SEMATTRS_GEN_AI_REQUEST_MODEL]: params?.model,
[SEMATTRS_GEN_AI_REQUEST_TEMPERATURE]: params?.temperature,
[SEMATTRS_GEN_AI_REQUEST_MAX_TOKENS]: params?.max_tokens,
[SEMATTRS_GEN_AI_REQUEST_TOP_P]: params?.top_p,
'gen_ai.request.top_k': params?.top_k,
[SEMATTRS_GEN_AI_OPERATION_NAME]: 'chat',
'lilypad.type': 'llm',
'server.address': 'api.anthropic.com',
},
},
activeContext,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like this span creation logic is duplicated 3 times in this file, can we refactor to DRY?

Comment on lines +663 to +687
if (chunk.type === 'message_start' && chunk.message) {
// Initial message with usage info
if (chunk.message.usage) {
usage.input_tokens = chunk.message.usage.input_tokens;
usage.output_tokens = chunk.message.usage.output_tokens;
}
} else if (chunk.type === 'content_block_delta' && chunk.delta) {
// Text content
if (chunk.delta.type === 'text_delta' && chunk.delta.text) {
content += chunk.delta.text;
}
} else if (chunk.type === 'message_delta' && chunk.delta) {
// Message delta with stop reason
if (chunk.delta.stop_reason) {
stopReason = chunk.delta.stop_reason;
}
} else if (chunk.type === 'message_stop' && chunk.usage) {
// Final usage info
usage.output_tokens = chunk.usage.output_tokens;
}

// Add chunk event
span.addEvent('gen_ai.chunk', {
size: chunk.delta?.text?.length || 0,
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

first copy of chunk processing

Comment on lines +745 to +764
if (chunk.type === 'message_start' && chunk.message) {
// Initial message with usage info
if (chunk.message.usage) {
usage.input_tokens = chunk.message.usage.input_tokens;
usage.output_tokens = chunk.message.usage.output_tokens;
}
} else if (chunk.type === 'content_block_delta' && chunk.delta) {
// Text content
if (chunk.delta.type === 'text_delta' && chunk.delta.text) {
content += chunk.delta.text;
}
} else if (chunk.type === 'message_delta' && chunk.delta) {
// Message delta with stop reason
if (chunk.delta.stop_reason) {
stopReason = chunk.delta.stop_reason;
}
} else if (chunk.type === 'message_stop' && chunk.usage) {
// Final usage info
usage.output_tokens = chunk.usage.output_tokens;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

second copy of chunk processing

Comment on lines +600 to +619
// Record system message if present
if (params?.system) {
span.addEvent('gen_ai.system.message', {
'gen_ai.system': 'anthropic',
content: params.system,
});
}

// Record messages
if (params?.messages) {
params.messages.forEach((message: any) => {
const content =
typeof message.content === 'string' ? message.content : JSON.stringify(message.content);

span.addEvent(`gen_ai.${message.role}.message`, {
'gen_ai.system': 'anthropic',
content: content,
});
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

second copy of message recording logic

Comment on lines +951 to +972
// Record system message if present
if (params?.system) {
span.addEvent('gen_ai.system.message', {
'gen_ai.system': 'anthropic',
content: params.system,
});
}

// Record messages
if (params?.messages) {
params.messages.forEach((message: any) => {
const content =
typeof message.content === 'string'
? message.content
: JSON.stringify(message.content);

span.addEvent(`gen_ai.${message.role}.message`, {
'gen_ai.system': 'anthropic',
content: content,
});
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

third copy of message recording logic

Comment on lines +252 to +273
// Record system message if present
if (params?.system) {
span.addEvent('gen_ai.system.message', {
'gen_ai.system': 'anthropic',
content: params.system,
});
}

// Record messages
if (params?.messages) {
params.messages.forEach((message: any) => {
const content =
typeof message.content === 'string'
? message.content
: JSON.stringify(message.content);

span.addEvent(`gen_ai.${message.role}.message`, {
'gen_ai.system': 'anthropic',
content: content,
});
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

first copy of message recording logic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants