mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
feat: Introduce Redis-backed stream services for enhanced job management
- 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.
This commit is contained in:
parent
c14b4f3e1f
commit
c611fad1d0
9 changed files with 835 additions and 49 deletions
130
packages/api/src/stream/createStreamServices.ts
Normal file
130
packages/api/src/stream/createStreamServices.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue