mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 09:50:15 +01:00
- Added createStreamServices function to configure job store and event transport, supporting both Redis and in-memory options. - Updated GenerationJobManager to allow configuration with custom job stores and event transports, improving flexibility for different deployment scenarios. - Refactored IJobStore interface to support asynchronous content retrieval, ensuring compatibility with Redis implementations. - Implemented RedisEventTransport for real-time event delivery across instances, enhancing scalability and responsiveness. - Updated InMemoryJobStore to align with new async patterns for content and run step retrieval, ensuring consistent behavior across storage options.
130 lines
3.8 KiB
TypeScript
130 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,
|
|
};
|
|
}
|