mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 09:50:15 +01:00
131 lines
3.8 KiB
TypeScript
131 lines
3.8 KiB
TypeScript
|
|
import type { Redis, Cluster } from 'ioredis';
|
||
|
|
import { logger } from '@librechat/data-schemas';
|
||
|
|
import type { IJobStore, IEventTransport } from './interfaces/IJobStore';
|
||
|
|
import { InMemoryJobStore } from './implementations/InMemoryJobStore';
|
||
|
|
import { InMemoryEventTransport } from './implementations/InMemoryEventTransport';
|
||
|
|
import { RedisJobStore } from './implementations/RedisJobStore';
|
||
|
|
import { RedisEventTransport } from './implementations/RedisEventTransport';
|
||
|
|
import { cacheConfig } from '~/cache/cacheConfig';
|
||
|
|
import { ioredisClient } from '~/cache/redisClients';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Configuration for stream services (optional overrides)
|
||
|
|
*/
|
||
|
|
export interface StreamServicesConfig {
|
||
|
|
/**
|
||
|
|
* Override Redis detection. If not provided, uses cacheConfig.USE_REDIS.
|
||
|
|
*/
|
||
|
|
useRedis?: boolean;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Override Redis client. If not provided, uses ioredisClient from cache.
|
||
|
|
*/
|
||
|
|
redisClient?: Redis | Cluster | null;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Dedicated Redis client for pub/sub subscribing.
|
||
|
|
* If not provided, will duplicate the main client.
|
||
|
|
*/
|
||
|
|
redisSubscriber?: Redis | Cluster | null;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Options for in-memory job store
|
||
|
|
*/
|
||
|
|
inMemoryOptions?: {
|
||
|
|
ttlAfterComplete?: number;
|
||
|
|
maxJobs?: number;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Stream services result
|
||
|
|
*/
|
||
|
|
export interface StreamServices {
|
||
|
|
jobStore: IJobStore;
|
||
|
|
eventTransport: IEventTransport;
|
||
|
|
isRedis: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create stream services (job store + event transport).
|
||
|
|
*
|
||
|
|
* Automatically detects Redis from cacheConfig.USE_REDIS and uses
|
||
|
|
* the existing ioredisClient. Falls back to in-memory if Redis
|
||
|
|
* is not configured or not available.
|
||
|
|
*
|
||
|
|
* @example Auto-detect (uses cacheConfig)
|
||
|
|
* ```ts
|
||
|
|
* const services = createStreamServices();
|
||
|
|
* // Uses Redis if USE_REDIS=true, otherwise in-memory
|
||
|
|
* ```
|
||
|
|
*
|
||
|
|
* @example Force in-memory
|
||
|
|
* ```ts
|
||
|
|
* const services = createStreamServices({ useRedis: false });
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export function createStreamServices(config: StreamServicesConfig = {}): StreamServices {
|
||
|
|
// Use provided config or fall back to cache config
|
||
|
|
const useRedis = config.useRedis ?? cacheConfig.USE_REDIS;
|
||
|
|
const redisClient = config.redisClient ?? ioredisClient;
|
||
|
|
const { redisSubscriber, inMemoryOptions } = config;
|
||
|
|
|
||
|
|
// Check if we should and can use Redis
|
||
|
|
if (useRedis && redisClient) {
|
||
|
|
try {
|
||
|
|
// For subscribing, we need a dedicated connection
|
||
|
|
// If subscriber not provided, duplicate the main client
|
||
|
|
let subscriber = redisSubscriber;
|
||
|
|
|
||
|
|
if (!subscriber && 'duplicate' in redisClient) {
|
||
|
|
subscriber = (redisClient as Redis).duplicate();
|
||
|
|
logger.info('[StreamServices] Duplicated Redis client for subscriber');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!subscriber) {
|
||
|
|
logger.warn('[StreamServices] No subscriber client available, falling back to in-memory');
|
||
|
|
return createInMemoryServices(inMemoryOptions);
|
||
|
|
}
|
||
|
|
|
||
|
|
const jobStore = new RedisJobStore(redisClient);
|
||
|
|
const eventTransport = new RedisEventTransport(redisClient, subscriber);
|
||
|
|
|
||
|
|
logger.info('[StreamServices] Created Redis-backed stream services');
|
||
|
|
|
||
|
|
return {
|
||
|
|
jobStore,
|
||
|
|
eventTransport,
|
||
|
|
isRedis: true,
|
||
|
|
};
|
||
|
|
} catch (err) {
|
||
|
|
logger.error(
|
||
|
|
'[StreamServices] Failed to create Redis services, falling back to in-memory:',
|
||
|
|
err,
|
||
|
|
);
|
||
|
|
return createInMemoryServices(inMemoryOptions);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return createInMemoryServices(inMemoryOptions);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create in-memory stream services
|
||
|
|
*/
|
||
|
|
function createInMemoryServices(options?: StreamServicesConfig['inMemoryOptions']): StreamServices {
|
||
|
|
const jobStore = new InMemoryJobStore({
|
||
|
|
ttlAfterComplete: options?.ttlAfterComplete ?? 300000, // 5 minutes
|
||
|
|
maxJobs: options?.maxJobs ?? 1000,
|
||
|
|
});
|
||
|
|
|
||
|
|
const eventTransport = new InMemoryEventTransport();
|
||
|
|
|
||
|
|
logger.info('[StreamServices] Created in-memory stream services');
|
||
|
|
|
||
|
|
return {
|
||
|
|
jobStore,
|
||
|
|
eventTransport,
|
||
|
|
isRedis: false,
|
||
|
|
};
|
||
|
|
}
|