Perplexica/src/lib/providers/anthropic.ts

158 lines
5.3 KiB
TypeScript
Raw Normal View History

2024-07-15 21:20:16 +05:30
import { ChatAnthropic } from '@langchain/anthropic';
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
2024-07-15 21:20:16 +05:30
import { getAnthropicApiKey } from '../../config';
import logger from '../../utils/logger';
import axios from 'axios';
2024-07-15 21:20:16 +05:30
2024-08-19 22:47:21 -04:00
class RetryingChatAnthropic extends ChatAnthropic {
maxRetries: number;
baseDelay: number;
constructor(config: ConstructorParameters<typeof ChatAnthropic>[0] & { maxRetries?: number; baseDelay?: number }) {
2024-08-19 22:47:21 -04:00
super(config);
this.maxRetries = config.maxRetries || 3;
this.baseDelay = config.baseDelay || 1000;
2024-08-19 22:47:21 -04:00
}
async call(messages: BaseMessage[], options?: this['ParsedCallOptions']): Promise<BaseMessage> {
let lastError: unknown;
2024-08-19 22:47:21 -04:00
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
logger.info(`Attempt ${attempt + 1}: Making API call to Anthropic...`);
const result = await super.call(messages, options);
logger.info(`Attempt ${attempt + 1}: API call successful.`);
return result;
2024-08-19 22:47:21 -04:00
} catch (error) {
lastError = error;
logger.error(`Full error object: ${JSON.stringify(error, Object.getOwnPropertyNames(error))}`);
if (this.isRetryableError(error) && attempt < this.maxRetries) {
const delay = this.calculateDelay(attempt);
logger.warn(`Attempt ${attempt + 1}: Retryable error occurred. Retrying in ${delay}ms.`);
await this.wait(delay);
} else {
logger.error(`Attempt ${attempt + 1}: Non-retryable error or max retries reached.`);
throw error;
}
2024-08-19 22:47:21 -04:00
}
}
throw lastError;
2024-08-19 22:47:21 -04:00
}
private isRetryableError(error: unknown): boolean {
if (error instanceof Error) {
// Check for network-related errors
if (error.message.includes('APIConnectionError') ||
error.message.includes('ETIMEDOUT') ||
error.message.includes('ENOTFOUND') ||
error.message.includes('EBADF') ||
error.message.includes('Connection error')) {
return true;
}
// Check for specific API errors that might be retryable
if (error.message.includes('rate limit') ||
error.message.includes('timeout') ||
error.message.includes('temporary failure')) {
return true;
}
}
// Check for Anthropic-specific error types
const anthropicError = error as any;
if (anthropicError.type === 'not_found_error' ||
anthropicError.type === 'service_unavailable' ||
anthropicError.type === 'timeout_error') {
return true;
}
return false;
}
private calculateDelay(attempt: number): number {
const jitter = Math.random() * 0.1 * this.baseDelay;
return Math.min((Math.pow(2, attempt) * this.baseDelay) + jitter, 30000);
2024-08-19 22:47:21 -04:00
}
private wait(ms: number): Promise<void> {
2024-08-19 22:47:21 -04:00
return new Promise(resolve => setTimeout(resolve, ms));
}
}
2024-07-15 21:20:16 +05:30
export const loadAnthropicChatModels = async () => {
const anthropicApiKey = getAnthropicApiKey();
2024-08-19 22:47:21 -04:00
if (!anthropicApiKey) {
logger.warn('Anthropic API key not found');
return {};
}
2024-07-15 21:20:16 +05:30
2024-08-19 22:47:21 -04:00
const modelConfigs = [
{ name: 'Claude 3.5 Sonnet', model: 'claude-3-5-sonnet-20240620' },
{ name: 'Claude 3 Opus', model: 'claude-3-opus-20240229' },
{ name: 'Claude 3 Sonnet', model: 'claude-3-sonnet-20240229' },
{ name: 'Claude 3 Haiku', model: 'claude-3-haiku-20240307' }
];
2024-07-15 21:20:16 +05:30
const chatModels: Record<string, RetryingChatAnthropic> = {};
2024-08-19 22:47:21 -04:00
for (const config of modelConfigs) {
try {
chatModels[config.name] = new RetryingChatAnthropic({
2024-08-19 22:47:32 -04:00
temperature: 0.5,
2024-08-19 22:47:21 -04:00
anthropicApiKey,
modelName: config.model,
2024-08-19 22:47:21 -04:00
maxRetries: 3,
baseDelay: 1000,
});
logger.info(`Successfully initialized ${config.name} with retry logic`);
} catch (err) {
logger.error(`Error initializing ${config.name}: ${err instanceof Error ? err.message : String(err)}`);
2024-08-19 22:47:21 -04:00
}
2024-07-15 21:20:16 +05:30
}
2024-08-19 22:47:21 -04:00
return chatModels;
2024-07-15 21:20:16 +05:30
};
async function getPublicIP(): Promise<string> {
try {
const response = await axios.get('https://api.ipify.org?format=json');
return response.data.ip;
} catch (error) {
logger.error('Error fetching public IP:', error);
return 'Unknown';
}
}
// Example usage (can be commented out or removed if not needed)
async function testRetryLogic(): Promise<void> {
try {
const chatModels = await loadAnthropicChatModels();
const claude = chatModels['Claude 3.5 Sonnet'];
if (!claude) {
throw new Error('Claude 3.5 Sonnet model not initialized');
}
logger.info("Model initialized successfully. Attempting API call...");
const result = await claude.call([
new HumanMessage("Hello, Claude! Please provide a brief summary of your capabilities.")
]);
logger.info("\nAPI call succeeded. Response:");
logger.info(result.content);
} catch (error) {
logger.error("\nError occurred:");
if (error instanceof Error) {
logger.error("Error name:", error.name);
logger.error("Error message:", error.message);
logger.error("Error stack:", error.stack);
} else if (typeof error === 'object' && error !== null) {
logger.error("Anthropic API Error:", JSON.stringify(error, null, 2));
} else {
logger.error("Unexpected error:", error);
}
}
}
// Uncomment the following line to run the test
// testRetryLogic();