Skip to content

Commit 503a6ea

Browse files
feat: add Redis session support for scalable distributed memory (#1785)
Co-authored-by: Kazuhiro Sera <[email protected]>
1 parent 46d0d2f commit 503a6ea

File tree

10 files changed

+1757
-2
lines changed

10 files changed

+1757
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,6 @@ cython_debug/
144144
# PyPI configuration file
145145
.pypirc
146146
.aider*
147+
148+
# Redis database files
149+
dump.rdb

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pip install openai-agents
3131

3232
For voice support, install with the optional `voice` group: `pip install 'openai-agents[voice]'`.
3333

34+
For Redis session support, install with the optional `redis` group: `pip install 'openai-agents[redis]'`.
35+
3436
### uv
3537

3638
If you're familiar with [uv](https://docs.astral.sh/uv/), using the tool would be even similar:
@@ -42,6 +44,8 @@ uv add openai-agents
4244

4345
For voice support, install with the optional `voice` group: `uv add 'openai-agents[voice]'`.
4446

47+
For Redis session support, install with the optional `redis` group: `uv add 'openai-agents[redis]'`.
48+
4549
## Hello world example
4650

4751
```python
@@ -211,8 +215,13 @@ print(result.final_output) # "Approximately 39 million"
211215
```python
212216
from agents import Agent, Runner, SQLiteSession
213217

214-
# Custom SQLite database file
218+
# SQLite - file-based or in-memory database
215219
session = SQLiteSession("user_123", "conversations.db")
220+
221+
# Redis - for scalable, distributed deployments
222+
# from agents.extensions.memory import RedisSession
223+
# session = RedisSession.from_url("user_123", url="redis://localhost:6379/0")
224+
216225
agent = Agent(name="Assistant")
217226

218227
# Different session IDs maintain separate conversation histories
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# `RedisSession`
2+
3+
::: agents.extensions.memory.redis_session.RedisSession
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
"""
2+
Example demonstrating Redis session memory functionality.
3+
4+
This example shows how to use Redis-backed session memory to maintain conversation
5+
history across multiple agent runs with persistence and scalability.
6+
7+
Note: This example clears the session at the start to ensure a clean demonstration.
8+
In production, you may want to preserve existing conversation history.
9+
"""
10+
11+
import asyncio
12+
13+
from agents import Agent, Runner
14+
from agents.extensions.memory import RedisSession
15+
16+
17+
async def main():
18+
# Create an agent
19+
agent = Agent(
20+
name="Assistant",
21+
instructions="Reply very concisely.",
22+
)
23+
24+
print("=== Redis Session Example ===")
25+
print("This example requires Redis to be running on localhost:6379")
26+
print("Start Redis with: redis-server")
27+
print()
28+
29+
# Create a Redis session instance
30+
session_id = "redis_conversation_123"
31+
try:
32+
session = RedisSession.from_url(
33+
session_id,
34+
url="redis://localhost:6379/0", # Use database 0
35+
)
36+
37+
# Test Redis connectivity
38+
if not await session.ping():
39+
print("Redis server is not available!")
40+
print("Please start Redis server and try again.")
41+
return
42+
43+
print("Connected to Redis successfully!")
44+
print(f"Session ID: {session_id}")
45+
46+
# Clear any existing session data for a clean start
47+
await session.clear_session()
48+
print("Session cleared for clean demonstration.")
49+
print("The agent will remember previous messages automatically.\n")
50+
51+
# First turn
52+
print("First turn:")
53+
print("User: What city is the Golden Gate Bridge in?")
54+
result = await Runner.run(
55+
agent,
56+
"What city is the Golden Gate Bridge in?",
57+
session=session,
58+
)
59+
print(f"Assistant: {result.final_output}")
60+
print()
61+
62+
# Second turn - the agent will remember the previous conversation
63+
print("Second turn:")
64+
print("User: What state is it in?")
65+
result = await Runner.run(agent, "What state is it in?", session=session)
66+
print(f"Assistant: {result.final_output}")
67+
print()
68+
69+
# Third turn - continuing the conversation
70+
print("Third turn:")
71+
print("User: What's the population of that state?")
72+
result = await Runner.run(
73+
agent,
74+
"What's the population of that state?",
75+
session=session,
76+
)
77+
print(f"Assistant: {result.final_output}")
78+
print()
79+
80+
print("=== Conversation Complete ===")
81+
print("Notice how the agent remembered the context from previous turns!")
82+
print("Redis session automatically handles conversation history with persistence.")
83+
84+
# Demonstrate session persistence
85+
print("\n=== Session Persistence Demo ===")
86+
all_items = await session.get_items()
87+
print(f"Total messages stored in Redis: {len(all_items)}")
88+
89+
# Demonstrate the limit parameter
90+
print("\n=== Latest Items Demo ===")
91+
latest_items = await session.get_items(limit=2)
92+
print("Latest 2 items:")
93+
for i, msg in enumerate(latest_items, 1):
94+
role = msg.get("role", "unknown")
95+
content = msg.get("content", "")
96+
print(f" {i}. {role}: {content}")
97+
98+
# Demonstrate session isolation with a new session
99+
print("\n=== Session Isolation Demo ===")
100+
new_session = RedisSession.from_url(
101+
"different_conversation_456",
102+
url="redis://localhost:6379/0",
103+
)
104+
105+
print("Creating a new session with different ID...")
106+
result = await Runner.run(
107+
agent,
108+
"Hello, this is a new conversation!",
109+
session=new_session,
110+
)
111+
print(f"New session response: {result.final_output}")
112+
113+
# Show that sessions are isolated
114+
original_items = await session.get_items()
115+
new_items = await new_session.get_items()
116+
print(f"Original session has {len(original_items)} items")
117+
print(f"New session has {len(new_items)} items")
118+
print("Sessions are completely isolated!")
119+
120+
# Clean up the new session
121+
await new_session.clear_session()
122+
await new_session.close()
123+
124+
# Optional: Demonstrate TTL (time-to-live) functionality
125+
print("\n=== TTL Demo ===")
126+
ttl_session = RedisSession.from_url(
127+
"ttl_demo_session",
128+
url="redis://localhost:6379/0",
129+
ttl=3600, # 1 hour TTL
130+
)
131+
132+
await Runner.run(
133+
agent,
134+
"This message will expire in 1 hour",
135+
session=ttl_session,
136+
)
137+
print("Created session with 1-hour TTL - messages will auto-expire")
138+
139+
await ttl_session.close()
140+
141+
# Close the main session
142+
await session.close()
143+
144+
except Exception as e:
145+
print(f"Error: {e}")
146+
print("Make sure Redis is running on localhost:6379")
147+
148+
149+
async def demonstrate_advanced_features():
150+
"""Demonstrate advanced Redis session features."""
151+
print("\n=== Advanced Features Demo ===")
152+
153+
# Custom key prefix for multi-tenancy
154+
tenant_session = RedisSession.from_url(
155+
"user_123",
156+
url="redis://localhost:6379/0",
157+
key_prefix="tenant_abc:sessions", # Custom prefix for isolation
158+
)
159+
160+
try:
161+
if await tenant_session.ping():
162+
print("Custom key prefix demo:")
163+
await Runner.run(
164+
Agent(name="Support", instructions="Be helpful"),
165+
"Hello from tenant ABC",
166+
session=tenant_session,
167+
)
168+
print("Session with custom key prefix created successfully")
169+
170+
await tenant_session.close()
171+
except Exception as e:
172+
print(f"Advanced features error: {e}")
173+
174+
175+
if __name__ == "__main__":
176+
asyncio.run(main())
177+
asyncio.run(demonstrate_advanced_features())

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ litellm = ["litellm>=1.67.4.post1, <2"]
4040
realtime = ["websockets>=15.0, <16"]
4141
sqlalchemy = ["SQLAlchemy>=2.0", "asyncpg>=0.29.0"]
4242
encrypt = ["cryptography>=45.0, <46"]
43+
redis = ["redis>=6.4.0"]
4344

4445
[dependency-groups]
4546
dev = [
@@ -67,6 +68,7 @@ dev = [
6768
"fastapi >= 0.110.0, <1",
6869
"aiosqlite>=0.21.0",
6970
"cryptography>=45.0, <46",
71+
"fakeredis>=2.31.3",
7072
]
7173

7274
[tool.uv.workspace]

src/agents/extensions/memory/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
__all__: list[str] = [
1414
"EncryptedSession",
15+
"RedisSession",
1516
"SQLAlchemySession",
1617
"AdvancedSQLiteSession",
1718
]
@@ -29,6 +30,17 @@ def __getattr__(name: str) -> Any:
2930
"Install it with: pip install openai-agents[encrypt]"
3031
) from e
3132

33+
if name == "RedisSession":
34+
try:
35+
from .redis_session import RedisSession # noqa: F401
36+
37+
return RedisSession
38+
except ModuleNotFoundError as e:
39+
raise ImportError(
40+
"RedisSession requires the 'redis' extra. "
41+
"Install it with: pip install openai-agents[redis]"
42+
) from e
43+
3244
if name == "SQLAlchemySession":
3345
try:
3446
from .sqlalchemy_session import SQLAlchemySession # noqa: F401

0 commit comments

Comments
 (0)