UNPKG

promptly-ai

Version:

A universal template-based prompt management system for LLM applications

202 lines 7.68 kB
"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