Skip to content

Commit b2b5b94

Browse files
committed
Implement caching for system prompt in AsyncHTTPClient and HTTPClient to reduce payload size
1 parent db61150 commit b2b5b94

4 files changed

Lines changed: 74 additions & 8 deletions

File tree

recallrai/async_session.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,18 @@ async def get_context(
136136
TimeoutError: If the request times out.
137137
RecallrAIError: For other API-related errors.
138138
"""
139+
# Fetch and cache the system prompt client-side to avoid sending ~20 KB on every request
140+
system_prompt_text: Optional[str] = None
141+
if include_system_prompt:
142+
system_prompt_text = await self._http.get_cached_system_prompt()
143+
139144
params = {
140145
"recall_strategy": recall_strategy.value,
141146
"min_top_k": min_top_k,
142147
"max_top_k": max_top_k,
143148
"memories_threshold": memories_threshold,
144149
"summaries_threshold": summaries_threshold,
145-
"include_system_prompt": include_system_prompt,
150+
"include_system_prompt": False,
146151
"include_metadata_ids": include_metadata_ids,
147152
}
148153
if last_n_messages is not None:
@@ -173,7 +178,10 @@ async def get_context(
173178
logger.warning("You are trying to get context for a processed session. Why do you need it?")
174179
elif self.status == SessionStatus.PROCESSING:
175180
logger.warning("You are trying to get context for a processing session. Why do you need it?")
176-
return ContextResponse.from_api_response(response.json())
181+
result = ContextResponse.from_api_response(response.json())
182+
if system_prompt_text is not None and result.context is not None:
183+
result = result.model_copy(update={"context": system_prompt_text + "\n\n\n" + result.context})
184+
return result
177185

178186
async def get_context_stream(
179187
self,
@@ -206,13 +214,18 @@ async def get_context_stream(
206214
Yields:
207215
Streaming ContextResponse objects with status updates and final context.
208216
"""
217+
# Fetch and cache the system prompt client-side to avoid sending ~20 KB on every request
218+
system_prompt_text: Optional[str] = None
219+
if include_system_prompt:
220+
system_prompt_text = await self._http.get_cached_system_prompt()
221+
209222
params = {
210223
"recall_strategy": recall_strategy.value,
211224
"min_top_k": min_top_k,
212225
"max_top_k": max_top_k,
213226
"memories_threshold": memories_threshold,
214227
"summaries_threshold": summaries_threshold,
215-
"include_system_prompt": include_system_prompt,
228+
"include_system_prompt": False,
216229
"include_metadata_ids": include_metadata_ids,
217230
"stream": True,
218231
}
@@ -233,7 +246,10 @@ async def get_context_stream(
233246
if not payload:
234247
continue
235248
data = json.loads(payload)
236-
yield ContextResponse.from_api_response(data)
249+
event = ContextResponse.from_api_response(data)
250+
if system_prompt_text is not None and event.is_final and event.context is not None:
251+
event = event.model_copy(update={"context": system_prompt_text + "\n\n\n" + event.context})
252+
yield event
237253

238254
async def update(self, new_metadata: Optional[Dict[str, Any]] = None) -> None:
239255
"""

recallrai/session.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,18 @@ def get_context(
135135
TimeoutError: If the request times out.
136136
RecallrAIError: For other API-related errors.
137137
"""
138+
# Fetch and cache the system prompt client-side to avoid sending ~20 KB on every request
139+
system_prompt_text: Optional[str] = None
140+
if include_system_prompt:
141+
system_prompt_text = self._http.get_cached_system_prompt()
142+
138143
params = {
139144
"recall_strategy": recall_strategy.value,
140145
"min_top_k": min_top_k,
141146
"max_top_k": max_top_k,
142147
"memories_threshold": memories_threshold,
143148
"summaries_threshold": summaries_threshold,
144-
"include_system_prompt": include_system_prompt,
149+
"include_system_prompt": False,
145150
"include_metadata_ids": include_metadata_ids,
146151
}
147152
if last_n_messages is not None:
@@ -172,7 +177,10 @@ def get_context(
172177
logger.warning("You are trying to get context for a processed session. Why do you need it?")
173178
elif self.status == SessionStatus.PROCESSING:
174179
logger.warning("You are trying to get context for a processing session. Why do you need it?")
175-
return ContextResponse.from_api_response(response.json())
180+
result = ContextResponse.from_api_response(response.json())
181+
if system_prompt_text is not None and result.context is not None:
182+
result = result.model_copy(update={"context": system_prompt_text + "\n\n\n" + result.context})
183+
return result
176184

177185
def get_context_stream(
178186
self,
@@ -205,13 +213,18 @@ def get_context_stream(
205213
Yields:
206214
Streaming ContextResponse objects with status updates and final context.
207215
"""
216+
# Fetch and cache the system prompt client-side to avoid sending ~20 KB on every request
217+
system_prompt_text: Optional[str] = None
218+
if include_system_prompt:
219+
system_prompt_text = self._http.get_cached_system_prompt()
220+
208221
params = {
209222
"recall_strategy": recall_strategy.value,
210223
"min_top_k": min_top_k,
211224
"max_top_k": max_top_k,
212225
"memories_threshold": memories_threshold,
213226
"summaries_threshold": summaries_threshold,
214-
"include_system_prompt": include_system_prompt,
227+
"include_system_prompt": False,
215228
"stream": True,
216229
"include_metadata_ids": include_metadata_ids,
217230
}
@@ -232,7 +245,10 @@ def get_context_stream(
232245
if not payload:
233246
continue
234247
data = json.loads(payload)
235-
yield ContextResponse.from_api_response(data)
248+
event = ContextResponse.from_api_response(data)
249+
if system_prompt_text is not None and event.is_final and event.context is not None:
250+
event = event.model_copy(update={"context": system_prompt_text + "\n\n\n" + event.context})
251+
yield event
236252

237253
def delete(self) -> None:
238254
"""

recallrai/utils/async_http_client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Async HTTP client for making requests to the RecallrAI API.
33
"""
44

5+
import time
56
from json import JSONDecodeError
67
from typing import Any, AsyncIterator, Dict, Optional
78
from httpx import Response, AsyncClient, TimeoutException, ConnectError, Limits
@@ -40,6 +41,22 @@ def __init__(
4041
self.base_url = base_url.rstrip("/")
4142
self.timeout = timeout
4243
self._client: Optional[AsyncClient] = None
44+
self._system_prompt_cache: Optional[str] = None
45+
self._system_prompt_cache_expires_at: float = 0.0
46+
47+
async def get_cached_system_prompt(self) -> str:
48+
"""Fetch the global system prompt, using a 1-hour in-memory cache to
49+
avoid shipping the ~20 KB prompt on every request."""
50+
now = time.monotonic()
51+
if (
52+
self._system_prompt_cache is not None
53+
and now < self._system_prompt_cache_expires_at
54+
):
55+
return self._system_prompt_cache # type: ignore[return-value]
56+
response = await self.get("/api/v1/system-prompt")
57+
self._system_prompt_cache = response.json()["system_prompt"]
58+
self._system_prompt_cache_expires_at = now + 3600
59+
return self._system_prompt_cache # type: ignore[return-value]
4360

4461
async def __aenter__(self):
4562
"""Async context manager entry."""

recallrai/utils/http_client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
HTTP client for making requests to the RecallrAI API.
33
"""
44

5+
import time
56
from json import JSONDecodeError
67
from typing import Any, Dict, Iterator, Optional
78
from httpx import Response, Client, TimeoutException, ConnectError, Limits
@@ -56,6 +57,22 @@ def __init__(
5657
"User-Agent": "RecallrAI-Python-SDK/0.5.8",
5758
},
5859
)
60+
self._system_prompt_cache: Optional[str] = None
61+
self._system_prompt_cache_expires_at: float = 0.0
62+
63+
def get_cached_system_prompt(self) -> str:
64+
"""Fetch the global system prompt, using a 1-hour in-memory cache to
65+
avoid shipping the ~20 KB prompt on every request."""
66+
now = time.monotonic()
67+
if (
68+
self._system_prompt_cache is not None
69+
and now < self._system_prompt_cache_expires_at
70+
):
71+
return self._system_prompt_cache # type: ignore[return-value]
72+
response = self.get("/api/v1/system-prompt")
73+
self._system_prompt_cache = response.json()["system_prompt"]
74+
self._system_prompt_cache_expires_at = now + 3600
75+
return self._system_prompt_cache # type: ignore[return-value]
5976

6077
def request(
6178
self,

0 commit comments

Comments
 (0)