Skip to content

feat(node): Add Anthropic AI integration #17348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '147 KB',
limit: '148 KB',
},
{
name: '@sentry/node - without tracing',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as Sentry from '@sentry/node';
import { nodeContextIntegration } from '@sentry/node-core';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
tracesSampleRate: 1.0,
sendDefaultPii: false,
transport: loggingTransport,
integrations: [
Sentry.anthropicAIIntegration({
recordInputs: true,
recordOutputs: true,
}),
nodeContextIntegration(),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Sentry from '@sentry/node';
import { nodeContextIntegration } from '@sentry/node-core';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
tracesSampleRate: 1.0,
sendDefaultPii: true,
transport: loggingTransport,
integrations: [
Sentry.anthropicAIIntegration(),
nodeContextIntegration(),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as Sentry from '@sentry/node';
import { nodeContextIntegration } from '@sentry/node-core';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
tracesSampleRate: 1.0,
sendDefaultPii: false,
transport: loggingTransport,
// Force include the integration
integrations: [
Sentry.anthropicAIIntegration(),
nodeContextIntegration(),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { instrumentAnthropicAiClient } from '@sentry/core';
import * as Sentry from '@sentry/node';

class MockAnthropic {
constructor(config) {
this.apiKey = config.apiKey;

// Create messages object with create and countTokens methods
this.messages = {
create: this._messagesCreate.bind(this),
countTokens: this._messagesCountTokens.bind(this)
};

this.models = {
retrieve: this._modelsRetrieve.bind(this),
};
}

/**
* Create a mock message
*/
async _messagesCreate(params) {
// Simulate processing time
await new Promise(resolve => setTimeout(resolve, 10));

if (params.model === 'error-model') {
const error = new Error('Model not found');
error.status = 404;
error.headers = { 'x-request-id': 'mock-request-123' };
throw error;
}

return {
id: 'msg_mock123',
type: 'message',
model: params.model,
role: 'assistant',
content: [
{
type: 'text',
text: 'Hello from Anthropic mock!',
},
],
stop_reason: 'end_turn',
stop_sequence: null,
usage: {
input_tokens: 10,
output_tokens: 15,
},
};
}

async _messagesCountTokens() {
// Simulate processing time
await new Promise(resolve => setTimeout(resolve, 10));

// For countTokens, just return input_tokens
return {
input_tokens: 15
}
}

async _modelsRetrieve(modelId) {
// Simulate processing time
await new Promise(resolve => setTimeout(resolve, 10));

// Match what the actual implementation would return
return {
id: modelId,
name: modelId,
created_at: 1715145600,
model: modelId, // Add model field to match the check in addResponseAttributes
};
}
}

async function run() {
await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
const mockClient = new MockAnthropic({
apiKey: 'mock-api-key',
});

const client = instrumentAnthropicAiClient(mockClient);

// First test: basic message completion
await client.messages.create({
model: 'claude-3-haiku-20240307',
system: 'You are a helpful assistant.',
messages: [
{ role: 'user', content: 'What is the capital of France?' },
],
temperature: 0.7,
max_tokens: 100,
});

// Second test: error handling
try {
await client.messages.create({
model: 'error-model',
messages: [{ role: 'user', content: 'This will fail' }],
});
} catch {
// Error is expected and handled
}

// Third test: count tokens with cached tokens
await client.messages.countTokens({
model: 'claude-3-haiku-20240307',
messages: [
{ role: 'user', content: 'What is the capital of France?' },
],
});

// Fourth test: models.retrieve
await client.models.retrieve('claude-3-haiku-20240307');
});
}

run();
Loading
Loading