Skip to content

feat(cache): add streaming cache support for chat responses - #529

Merged
steebchen merged 2 commits into
mainfrom
feat/streaming-cache
Jul 27, 2025
Merged

steebchen merged 2 commits into
mainfrom
feat/streaming-cache

Conversation

@steebchen

@steebchen steebchen commented Jul 26, 2025 •

Copy link
Copy Markdown
Member

Integrated caching support for streaming chat responses. Added utility functions setStreamingCache, getStreamingCache, and generateStreamingCacheKey. Enhanced chat logic to handle streaming response caching, including replaying cached chunks and saving streamed data. Improved logging with reconstructed content from cached data.

Summary by CodeRabbit

  • New Features

    • Added caching support for streaming chat completions, enabling instant replay of streamed responses when available.
    • Improved chat performance by storing and retrieving streamed chat data for repeated requests.
  • Bug Fixes

    • Enhanced reliability of chat streaming by capturing and replaying all streamed chunks consistently.
  • Chores

    • Updated internal caching mechanisms to support both streaming and non-streaming chat responses.

Integrated caching support for streaming chat responses. Added utility
functions `setStreamingCache`, `getStreamingCache`, and
`generateStreamingCacheKey`. Enhanced chat logic to handle streaming
response caching, including replaying cached chunks and saving streamed
data. Improved logging with reconstructed content from cached data.
@coderabbitai

coderabbitai Bot commented Jul 26, 2025 •

Copy link
Copy Markdown
Contributor

"""

Walkthrough

The changes introduce a streaming cache mechanism for chat completions. The system now captures and stores each streamed chunk of a chat response, along with metadata, enabling replay of entire streaming responses from cache. This involves new cache key generation, chunked data structures, and updates to both the chat handler and cache utility functions.

Changes

File(s) Change Summary
apps/gateway/src/chat/chat.ts Added logic to cache and replay streaming chat completions, including capturing streamed chunks, reconstructing responses, and updating SSE handling to support streaming cache.
apps/gateway/src/lib/cache.ts Introduced streaming cache interfaces and functions: chunk/metadata structures, streaming cache key generation, and set/get streaming cache operations in Redis.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ChatHandler
    participant Cache
    participant SSE

    Client->>ChatHandler: Request chat completion (streaming)
    ChatHandler->>Cache: getStreamingCache(key)
    alt Cache hit and completed
        Cache-->>ChatHandler: Cached streaming data
        ChatHandler->>SSE: Replay cached chunks to client
    else Cache miss
        ChatHandler->>SSE: Stream chunks to client (writeSSEAndCache)
        ChatHandler->>Cache: setStreamingCache(key, chunks, metadata)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–20 minutes

Possibly related PRs


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfadf28 and dcaac8d.

📒 Files selected for processing (1)
  • apps/gateway/src/chat/chat.ts (15 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/gateway/src/chat/chat.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: e2e / run
  • GitHub Check: build / run
  • GitHub Check: autofix
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/streaming-cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/gateway/src/lib/cache.ts (2)

255-260: Export the interface for better type safety

Consider exporting this interface to ensure type safety when used in chat.ts and potential future consumers.

-interface StreamingCacheChunk {
+export interface StreamingCacheChunk {
 	data: string;
 	eventId: number;
 	event?: string;
 	timestamp: number;
 }

262-272: Export the interface and consider adding size validation

Export this interface for type safety and consider adding validation to prevent excessive memory usage from large chunk arrays.

-interface StreamingCacheData {
+export interface StreamingCacheData {
 	chunks: StreamingCacheChunk[];
 	metadata: {
 		model: string;
 		provider: string;
 		finishReason: string | null;
 		totalChunks: number;
 		duration: number;
 		completed: boolean;
 	};
 }

Consider implementing a maximum chunk limit or total size validation in setStreamingCache to prevent potential memory exhaustion from excessively large streaming responses.

apps/gateway/src/chat/chat.ts (1)

1885-1911: Add null check for streamingCacheKey

The function should validate that streamingCacheKey is not null before attempting to push chunks.

 				// Capture for streaming cache if enabled
-				if (cachingEnabled && streamingCacheKey) {
+				if (cachingEnabled && streamingCacheKey !== null) {
 					streamingChunks.push({
 						data: sseData.data,
 						eventId: sseData.id ? parseInt(sseData.id, 10) : eventId,
 						event: sseData.event,
 						timestamp: Date.now() - streamStartTime,
 					});
 				}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca97ba and cfadf28.

📒 Files selected for processing (2)
  • apps/gateway/src/chat/chat.ts (15 hunks)
  • apps/gateway/src/lib/cache.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Use localStorage instead of cookies for client-side data persistence

Files:

  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/lib/cache.ts
**/*.{js,ts}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always use db().query.<table>.findMany() or db().query.<table>.findFirst()

Files:

  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/lib/cache.ts
{apps/api,apps/gateway,packages/db}/**/*.ts

📄 CodeRabbit Inference Engine (CLAUDE.md)

{apps/api,apps/gateway,packages/db}/**/*.ts: Use Drizzle ORM with latest object syntax for database operations
For reads, use db().query.<table>.findMany() or db().query.<table>.findFirst()

Files:

  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/lib/cache.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use as any or : any in TypeScript files.

Files:

  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/lib/cache.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build / run
  • GitHub Check: e2e / run
  • GitHub Check: autofix
🔇 Additional comments (7)
apps/gateway/src/lib/cache.ts (2)

274-278: LGTM!

Clean implementation that properly reuses the existing cache key generation logic.


297-310: LGTM!

Well-implemented function that follows the established pattern with proper error handling and type safety.

apps/gateway/src/chat/chat.ts (5)

28-37: LGTM!

Imports are correctly added for the new streaming cache functionality.


1642-1654: LGTM!

Cache key generation correctly handles both streaming and non-streaming cases with a consistent payload structure.


1774-1825: LGTM!

Non-streaming cache handling correctly maintains backward compatibility with proper logging.


2735-2757: LGTM!

Streaming cache storage correctly saves completed streams with comprehensive metadata and proper error handling.


1982-1989: LGTM!

All SSE writes have been consistently replaced with the caching-aware helper function, ensuring complete capture of streaming responses.

Also applies to: 2006-2023, 2072-2088, 2181-2184, 2233-2237, 2350-2360, 2396-2399, 2664-2674

Comment thread apps/gateway/src/chat/chat.ts
Comment on lines +280 to +295
export async function setStreamingCache(
key: string,
data: StreamingCacheData,
expirationSeconds: number,
): Promise<void> {
if (process.env.NODE_ENV === "test") {
// temp disable caching in test mode
return;
}

try {
await redisClient.set(key, JSON.stringify(data), "EX", expirationSeconds);
} catch (error) {
console.error("Error setting streaming cache:", error);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for data integrity and size limits

Consider validating the streaming cache data before storage to ensure data integrity and prevent memory issues.

 export async function setStreamingCache(
 	key: string,
 	data: StreamingCacheData,
 	expirationSeconds: number,
 ): Promise<void> {
 	if (process.env.NODE_ENV === "test") {
 		// temp disable caching in test mode
 		return;
 	}
 
+	// Validate data structure
+	if (!data.chunks || !Array.isArray(data.chunks)) {
+		console.error("Invalid streaming cache data: chunks must be an array");
+		return;
+	}
+
+	// Limit maximum chunks to prevent memory issues (e.g., 10000 chunks)
+	const MAX_CHUNKS = 10000;
+	if (data.chunks.length > MAX_CHUNKS) {
+		console.error(`Streaming cache data exceeds maximum chunks limit: ${data.chunks.length} > ${MAX_CHUNKS}`);
+		return;
+	}
+
 	try {
 		await redisClient.set(key, JSON.stringify(data), "EX", expirationSeconds);
 	} catch (error) {
 		console.error("Error setting streaming cache:", error);
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function setStreamingCache(
key: string,
data: StreamingCacheData,
expirationSeconds: number,
): Promise<void> {
if (process.env.NODE_ENV === "test") {
// temp disable caching in test mode
return;
}
try {
await redisClient.set(key, JSON.stringify(data), "EX", expirationSeconds);
} catch (error) {
console.error("Error setting streaming cache:", error);
}
}
export async function setStreamingCache(
key: string,
data: StreamingCacheData,
expirationSeconds: number,
): Promise<void> {
if (process.env.NODE_ENV === "test") {
// temp disable caching in test mode
return;
}
// Validate data structure
if (!data.chunks || !Array.isArray(data.chunks)) {
console.error("Invalid streaming cache data: chunks must be an array");
return;
}
// Limit maximum chunks to prevent memory issues (e.g., 10000 chunks)
const MAX_CHUNKS = 10000;
if (data.chunks.length > MAX_CHUNKS) {
console.error(
`Streaming cache data exceeds maximum chunks limit: ${data.chunks.length} > ${MAX_CHUNKS}`
);
return;
}
try {
await redisClient.set(key, JSON.stringify(data), "EX", expirationSeconds);
} catch (error) {
console.error("Error setting streaming cache:", error);
}
}
🤖 Prompt for AI Agents
In apps/gateway/src/lib/cache.ts around lines 280 to 295, the setStreamingCache
function currently stores data without validation. Add checks before storing to
validate the data structure and ensure it does not exceed size limits to prevent
memory issues. Implement validation logic to verify required fields and data
types in the StreamingCacheData object and check the serialized data size before
calling redisClient.set, returning early or throwing an error if validation
fails.

Improved logic for accumulating and processing tool calls
from streaming chat chunks. Updated tool call schema to
record detailed data. Enhanced replay timing accuracy with
original delays capped for long waits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant