UNPKG

@quantumai/quantum-cli-core

Version:

Quantum CLI Core - Multi-LLM Collaboration System

220 lines 9.05 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { BaseLLMProvider, } from './base-provider.js'; import Anthropic from '@anthropic-ai/sdk'; export class AnthropicProvider extends BaseLLMProvider { id = 'anthropic'; capabilities = [ 'text-generation', 'creative-writing', 'streaming', 'conversation', 'code-generation', 'analysis', 'reasoning', 'safety-filtering', 'long-context', ]; client; modelName; maxTokens; temperature; constructor(providerConfig) { super(providerConfig); const apiKey = process.env[providerConfig.apiKeyEnvVar || 'ANTHROPIC_API_KEY']; if (!apiKey) { throw new Error(`Anthropic API key not found in environment variable: ${providerConfig.apiKeyEnvVar || 'ANTHROPIC_API_KEY'}`); } this.client = new Anthropic({ apiKey }); this.modelName = providerConfig.model || providerConfig.modelName || 'claude-3-5-sonnet-20241022'; this.maxTokens = providerConfig.maxTokens || 4000; this.temperature = providerConfig.temperature || 0.7; } async generate(prompt, options) { const startTime = Date.now(); try { const msg = await this.client.messages.create({ model: this.modelName, max_tokens: options?.maxTokens || this.maxTokens, temperature: options?.temperature || this.temperature, messages: [{ role: 'user', content: prompt }], }); const textContent = msg.content[0]; const content = textContent && 'text' in textContent ? textContent.text : ''; const latency = Date.now() - startTime; const tokenUsage = { inputTokens: msg.usage.input_tokens, outputTokens: msg.usage.output_tokens, totalTokens: msg.usage.input_tokens + msg.usage.output_tokens, }; const cost = this.calculateCost(tokenUsage); const confidence = 0.88; // Default confidence for Anthropic const safetyResult = await this.applySafetyFiltering(content, options); return { ...this.createResponse(content, latency, tokenUsage.totalTokens, cost, confidence), providerId: this.id, model: this.modelName, tokenUsage, safetyResult: { safe: safetyResult.isSafe, score: safetyResult.confidence, category: safetyResult.categories.join(', '), details: safetyResult.details, }, metadata: { model: this.modelName, latency, tokens: tokenUsage.totalTokens, cost, stopReason: msg.stop_reason, usage: msg.usage, }, }; } catch (error) { return this.createErrorResponse(error); } } async generateStream(prompt, options) { const startTime = Date.now(); try { const stream = await this.client.messages.create({ model: this.modelName, max_tokens: options?.maxTokens || this.maxTokens, temperature: options?.temperature || this.temperature, messages: [{ role: 'user', content: prompt }], stream: true, }); let content = ''; let inputTokens = 0; let outputTokens = 0; const self = this; // Capture context for generator return { async *[Symbol.asyncIterator]() { for await (const chunk of stream) { if (chunk.type === 'message_start') { inputTokens = chunk.message.usage.input_tokens; } else if (chunk.type === 'content_block_delta') { if ('text' in chunk.delta) { content += chunk.delta.text; yield { content: chunk.delta.text, isComplete: false, }; } } else if (chunk.type === 'message_delta') { outputTokens = chunk.usage.output_tokens; } else if (chunk.type === 'message_stop') { const latency = Date.now() - startTime; const tokenUsage = { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, }; yield { content: '', isComplete: true, metadata: { provider: 'anthropic', model: self.modelName, latency, tokenUsage, cost: self.calculateCost(tokenUsage), confidence: 0.88, }, }; } } }, }; } catch (error) { throw new Error(`Anthropic streaming failed: ${error.message}`); } } calculateCost(tokenUsage) { if (typeof tokenUsage === 'number') { // Legacy support for base class method signature return super.calculateCost(tokenUsage); } // Claude-3 pricing as of 2024 (per 1M tokens) // These are approximate rates - update with actual pricing const pricingRates = { 'claude-3-5-sonnet-20241022': { input: 3.0, output: 15.0 }, 'claude-3-5-haiku-20241022': { input: 0.25, output: 1.25 }, 'claude-3-opus-20240229': { input: 15.0, output: 75.0 }, 'claude-3-sonnet-20240229': { input: 3.0, output: 15.0 }, 'claude-3-haiku-20240307': { input: 0.25, output: 1.25 }, }; const rates = pricingRates[this.modelName] || pricingRates['claude-3-5-sonnet-20241022']; const inputCost = (tokenUsage.inputTokens / 1_000_000) * rates.input; const outputCost = (tokenUsage.outputTokens / 1_000_000) * rates.output; return inputCost + outputCost; } async applySafetyFiltering(content, options) { // Basic safety filtering implementation // In production, this would integrate with Anthropic's safety systems const safetyConfig = this.config.safetySettings; if (!safetyConfig?.enabled) { return { isSafe: true, confidence: 1.0, categories: [], details: 'Safety filtering disabled', }; } // Simple content filtering patterns const harmfulPatterns = [ /\b(violence|harm|hurt|kill|murder|suicide)\b/gi, /\b(hack|exploit|malware|virus)\b/gi, /\b(illegal|criminal|fraud|scam)\b/gi, ]; const detectedIssues = []; let maxSeverity = 0; for (const pattern of harmfulPatterns) { const matches = content.match(pattern); if (matches) { detectedIssues.push(`Potentially harmful content detected: ${matches[0]}`); maxSeverity = Math.max(maxSeverity, 0.7); } } const filterLevel = safetyConfig.filterLevel || 'medium'; const threshold = filterLevel === 'high' ? 0.3 : filterLevel === 'medium' ? 0.5 : 0.8; const isSafe = maxSeverity < threshold; return { isSafe, confidence: isSafe ? 0.95 : 0.6, categories: detectedIssues.length > 0 ? ['content-policy'] : [], details: detectedIssues.length > 0 ? detectedIssues.join('; ') : 'Content appears safe', severity: maxSeverity, }; } async validateCredentials() { try { // Attempt a small request to validate the API key await this.client.messages.create({ model: this.modelName, max_tokens: 1, messages: [{ role: 'user', content: 'Hello' }], }); return true; } catch (error) { console.error(`Anthropic credential validation failed: ${error.message}`); return false; } } } //# sourceMappingURL=anthropic-provider.js.map