PRIX / lib /src /bot.js
yamxxx1's picture
Upload 139 files
7da7e2c verified
Raw
History Blame
9.11 kB
"use strict";
// Node 20 includes native fetch support
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Bot = void 0;
// AI Client for Groq with Failover Support + Circuit Breaker
// Fallback shims for Probot runtime
/* eslint-disable no-console */
const chatgpt_1 = require("chatgpt");
const p_retry_1 = __importDefault(require("p-retry"));
const opossum_1 = __importDefault(require("opossum"));
const options_1 = require("./options");
const limits_1 = require("./limits");
const pino_1 = __importDefault(require("pino"));
const logger = (0, pino_1.default)({ level: process.env.LOG_LEVEL || 'info' });
// Circuit breaker configuration
const CIRCUIT_BREAKER_OPTIONS = {
timeout: 60000, // 60 seconds
errorThresholdPercentage: 50,
resetTimeout: 30000,
volumeThreshold: 5,
};
const logInfo = (msg) => logger.info(msg);
const logWarning = (msg) => logger.warn(msg);
const logError = (msg) => logger.error(msg);
const setFailed = (msg) => {
logger.error(msg);
process.exit(1);
};
class Bot {
api = null;
options;
aiOptions;
currentModel;
fallbackModels;
circuitBreaker = null;
constructor(options, aiOptions) {
this.options = options;
this.aiOptions = aiOptions;
this.currentModel = aiOptions.model;
// Setup fallback chain based on tier
if (options_1.Options.HEAVY_MODELS.includes(this.currentModel)) {
this.fallbackModels = [...options_1.Options.HEAVY_MODELS];
}
else {
this.fallbackModels = [...options_1.Options.LIGHT_MODELS];
}
// Remove current model from fallbacks to avoid immediate repeat
this.fallbackModels = this.fallbackModels.filter(m => m !== this.currentModel);
this.initClient();
this.initCircuitBreaker();
}
initCircuitBreaker() {
if (!this.api)
return;
// Wrap the sendMessage method with circuit breaker
this.circuitBreaker = new opossum_1.default(async ({ message, opts }) => {
return this.api.sendMessage(message, opts);
}, CIRCUIT_BREAKER_OPTIONS);
// Circuit breaker event handlers
this.circuitBreaker.on('open', () => {
logWarning(`Circuit breaker opened for model: ${this.currentModel}`);
});
this.circuitBreaker.on('halfOpen', () => {
logInfo(`Circuit breaker half-open for model: ${this.currentModel}`);
});
this.circuitBreaker.on('close', () => {
logInfo(`Circuit breaker closed for model: ${this.currentModel}`);
});
this.circuitBreaker.on('fallback', (_result) => {
logWarning(`Circuit breaker fallback executed for model: ${this.currentModel}`);
});
}
initClient() {
const apiKey = process.env.GROQ_API_KEY || process.env.AI_API_KEY;
if (apiKey) {
const currentDate = new Date().toISOString().split('T')[0];
const limits = new limits_1.TokenLimits(this.currentModel);
const systemMessage = `${this.options.systemMessage}
Knowledge cutoff: ${limits.knowledgeCutOff}
Current date: ${currentDate}
IMPORTANT: Entire response must be in ISO code: ${this.options.language}
`;
this.api = new chatgpt_1.ChatGPTAPI({
apiBaseUrl: this.options.apiBaseUrl,
systemMessage,
apiKey,
apiOrg: process.env.AI_API_ORG || undefined,
debug: this.options.debug,
maxModelTokens: limits.maxTokens,
maxResponseTokens: limits.responseTokens,
completionParams: {
temperature: this.options.modelTemperature,
model: this.currentModel
}
});
logInfo(`Initialized AI client with model: ${this.currentModel}`);
}
else {
throw new Error("Missing 'GROQ_API_KEY' or 'AI_API_KEY'");
}
}
chat = async (message, ids) => {
let res = ['', {}];
try {
res = await this.chat_(message, ids);
return res;
}
catch (e) {
if (e instanceof chatgpt_1.ChatGPTError) {
logWarning(`AI communication error: ${e.message}`);
}
return res;
}
};
chat_ = async (message, ids) => {
const start = Date.now();
if (!message)
return ['', {}];
let response;
if (this.api != null) {
const opts = {
timeoutMs: this.options.timeoutMS
};
if (ids.parentMessageId) {
opts.parentMessageId = ids.parentMessageId;
}
try {
// Check if circuit breaker is open
if (this.circuitBreaker?.opened) {
logWarning(`Circuit breaker is OPEN for ${this.currentModel}, attempting fallback...`);
if (this.fallbackModels.length > 0) {
const nextModel = this.fallbackModels.shift();
if (nextModel) {
this.currentModel = nextModel;
this.initClient();
this.initCircuitBreaker();
// Recursive retry with new model
return this.chat_(message, ids);
}
}
throw new Error(`Circuit breaker open and no fallback models available for ${this.currentModel}`);
}
const result = await (0, p_retry_1.default)(async () => {
if (!this.circuitBreaker) {
throw new Error('Circuit breaker not initialized');
}
try {
const messageResult = await this.circuitBreaker.fire({ message, opts });
return messageResult;
}
catch (e) {
// Check if circuit breaker opened
if (this.circuitBreaker.opened && this.fallbackModels.length > 0) {
const nextModel = this.fallbackModels.shift();
if (nextModel) {
logWarning(`Circuit breaker opened for ${this.currentModel}. Switching to fallback: ${nextModel}`);
this.currentModel = nextModel;
this.initClient();
this.initCircuitBreaker();
// Throw to trigger retry with new model
throw new Error(`Model switched to ${nextModel}, retrying...`);
}
}
// Handle rate limiting / context size
if ((e?.status === 429 || e?.status === 413) && this.fallbackModels.length > 0) {
const nextModel = this.fallbackModels.shift();
if (nextModel) {
logWarning(`Rate limit or context size error (${e.status}) hit for ${this.currentModel}. Switching to fallback: ${nextModel}`);
this.currentModel = nextModel;
this.initClient();
this.initCircuitBreaker();
throw new Error(`Model switched to ${nextModel}, retrying...`);
}
}
throw e;
}
}, {
retries: this.options.retries,
minTimeout: 20000, // 20s delay to allow TPM refresh
onFailedAttempt: (err) => {
logInfo(`Attempt ${err.attemptNumber} failed. ${err.retriesLeft} retries left. Error: ${err.message}`);
}
});
response = result;
}
catch (e) {
if (e instanceof chatgpt_1.ChatGPTError) {
logWarning(`AI Error after retries: ${e.message} (Last model used: ${this.currentModel})`);
}
else if (e instanceof Error) {
logError(`Failed to get AI response: ${e.message}`);
}
}
const end = Date.now();
logInfo(`AI response time: ${end - start} ms`);
}
else {
setFailed('AI client is not initialized');
}
let responseText = '';
if (response != null) {
responseText = response.text;
}
const newIds = {
parentMessageId: response?.id,
conversationId: response?.conversationId
};
return [responseText, newIds];
};
}
exports.Bot = Bot;
//# sourceMappingURL=bot.js.map