@quantumai/quantum-cli-core
Version:
Quantum CLI Core - Multi-LLM Collaboration System
220 lines • 9.05 kB
JavaScript
/**
* @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