promptly-ai
Version:
A universal template-based prompt management system for LLM applications
202 lines • 7.68 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnthropicAdapter = void 0;
class AnthropicRequestBuilder {
static build(config) {
const systemMessage = this.extractSystemMessage(config.messages);
const userMessages = this.extractUserMessages(config.messages);
const params = {
model: config.model,
max_tokens: config.maxTokens ?? this.DEFAULT_MAX_TOKENS,
temperature: config.temperature ?? this.DEFAULT_TEMPERATURE,
system: systemMessage?.content,
messages: userMessages,
};
if (config.maxThinkingTokens) {
params.thinking = {
type: 'enabled',
budget_tokens: config.maxThinkingTokens,
};
}
return params;
}
static extractSystemMessage(messages) {
return messages.find((msg) => msg.role === 'system');
}
static extractUserMessages(messages) {
return messages
.filter((msg) => msg.role !== 'system')
.map((msg) => ({
role: msg.role,
content: msg.content,
}));
}
}
AnthropicRequestBuilder.DEFAULT_MAX_TOKENS = 1000;
AnthropicRequestBuilder.DEFAULT_TEMPERATURE = 0.7;
class StreamingStrategy {
static shouldUseStreaming(config) {
const maxTokens = config.maxTokens ?? 1000;
const maxThinkingTokens = config.maxThinkingTokens ?? 0;
const totalMaxTokens = maxTokens + maxThinkingTokens;
return totalMaxTokens > this.STREAMING_THRESHOLD || maxThinkingTokens > 0;
}
}
StreamingStrategy.STREAMING_THRESHOLD = 10000;
class StreamProcessor {
constructor(model) {
this.content = '';
this.inputTokens = 0;
this.outputTokens = 0;
this.thinkingTokens = 0;
this.model = model;
}
async processStream(stream) {
for await (const chunk of stream) {
this.processChunk(chunk);
}
const costUsd = CostCalculator.calculateCost(this.model, this.inputTokens, this.outputTokens);
return {
content: this.content,
inputTokens: this.inputTokens,
outputTokens: this.outputTokens,
thinkingTokens: this.thinkingTokens,
costUsd,
};
}
processChunk(chunk) {
switch (chunk.type) {
case 'message_start':
if (chunk.message?.usage) {
this.inputTokens = chunk.message.usage.input_tokens;
}
break;
case 'content_block_delta':
if (chunk.delta?.type === 'text_delta' && chunk.delta.text) {
this.content += chunk.delta.text;
}
break;
case 'message_delta':
case 'message_stop':
if (chunk.usage) {
this.outputTokens = chunk.usage.output_tokens;
this.thinkingTokens = chunk.usage.thinking_tokens ?? 0;
}
break;
}
}
}
class CostCalculator {
static calculateCost(model, inputTokens, outputTokens) {
const pricing = this.PRICING[model];
if (!pricing) {
// Try to match by model family if exact model not found
if (model.includes('opus')) {
return ((inputTokens * 15.00) + (outputTokens * 75.00)) / 1000000;
}
else if (model.includes('sonnet')) {
return ((inputTokens * 3.00) + (outputTokens * 15.00)) / 1000000;
}
else if (model.includes('haiku')) {
return ((inputTokens * 0.80) + (outputTokens * 4.00)) / 1000000;
}
// Default to Sonnet pricing if model not recognized
return ((inputTokens * 3.00) + (outputTokens * 15.00)) / 1000000;
}
return ((inputTokens * pricing.input) + (outputTokens * pricing.output)) / 1000000;
}
}
// Anthropic pricing as of January 2025 (per million tokens)
CostCalculator.PRICING = {
// Claude 4 models
'claude-4-opus': { input: 15.00, output: 75.00 },
'claude-4-sonnet': { input: 3.00, output: 15.00 },
// Claude 3.5 models
'claude-3-5-haiku-20241022': { input: 0.80, output: 4.00 },
'claude-3-5-sonnet-20241022': { input: 3.00, output: 15.00 },
'claude-3-5-sonnet-20240620': { input: 3.00, output: 15.00 },
// Claude 3 models (legacy)
'claude-3-opus-20240229': { input: 15.00, output: 75.00 },
'claude-3-sonnet-20240229': { input: 3.00, output: 15.00 },
'claude-3-haiku-20240307': { input: 0.25, output: 1.25 },
};
class ResponseProcessor {
static processStandardResponse(response, model) {
const content = response.content[0];
if (content.type !== 'text') {
throw new Error('Unexpected response type from Anthropic');
}
const inputTokens = response.usage.input_tokens;
const outputTokens = response.usage.output_tokens;
const costUsd = CostCalculator.calculateCost(model, inputTokens, outputTokens);
return {
content: content.text,
inputTokens,
outputTokens,
thinkingTokens: response.usage.thinking_tokens ?? 0,
costUsd,
};
}
}
class AnthropicAdapter {
constructor(apiKey) {
this.name = 'anthropic';
this.validateApiKey(apiKey);
this.client = this.initializeClient(apiKey);
}
supportsModel(model) {
const patterns = [/^claude-/i];
return patterns.some((pattern) => pattern.test(model));
}
async generate(config) {
try {
const requestParams = AnthropicRequestBuilder.build(config);
const useStreaming = StreamingStrategy.shouldUseStreaming(config);
const result = useStreaming
? await this.generateWithStreaming(requestParams)
: await this.generateStandard(requestParams);
return this.buildResult(result, config.model);
}
catch (error) {
throw new Error(`Anthropic generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
validateApiKey(apiKey) {
if (!apiKey && !process.env.ANTHROPIC_API_KEY) {
throw new Error('Anthropic API key is required. Provide it as parameter or set ANTHROPIC_API_KEY environment variable.');
}
}
initializeClient(apiKey) {
try {
const Anthropic = require('@anthropic-ai/sdk');
return new Anthropic({
apiKey: apiKey || process.env.ANTHROPIC_API_KEY,
});
}
catch (error) {
throw new Error('Anthropic SDK not found. Install it with: npm install @anthropic-ai/sdk');
}
}
async generateWithStreaming(params) {
const stream = await this.client.messages.create({ ...params, stream: true });
const processor = new StreamProcessor(params.model);
return processor.processStream(stream);
}
async generateStandard(params) {
const response = await this.client.messages.create(params);
return ResponseProcessor.processStandardResponse(response, params.model);
}
buildResult(result, model) {
return {
content: result.content,
provider: this.name,
model,
inputTokens: result.inputTokens,
outputTokens: result.outputTokens,
thinkingTokens: result.thinkingTokens,
costUsd: result.costUsd,
};
}
}
exports.AnthropicAdapter = AnthropicAdapter;
//# sourceMappingURL=anthropic.js.map