Skip to content

Commit 8d5ba83

Browse files
committed
feat: add Redis session support for scalable distributed memory
- Add RedisSession class with full Session protocol implementation - Support Redis URL connection strings and direct client injection - Include TTL (time-to-live) support for automatic session expiration - Add key prefixes for multi-tenancy and namespace isolation - Implement atomic operations using Redis pipelines for data integrity - Add comprehensive test suite with in-memory fakeredis support - Include detailed example demonstrating Redis session features - Update documentation with Redis session reference - Add redis extra dependency group for easy installation The RedisSession enables production-grade, distributed session memory that scales across multiple application instances while maintaining full compatibility with the existing Session interface.
1 parent d91e39c commit 8d5ba83

File tree

11 files changed

+946
-2
lines changed

11 files changed

+946
-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

examples/basic/dynamic_system_prompt.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def custom_instructions(
2828
instructions=custom_instructions,
2929
)
3030

31+
3132
async def main():
3233
context = CustomContext(style=random.choice(["haiku", "pirate", "robot"]))
3334
print(f"Using style: {context.style}\n")

examples/basic/tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def get_weather(city: Annotated[str, "The city to get the weather for"]) -> Weat
1818
print("[debug] get_weather called")
1919
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")
2020

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

@@ -28,6 +29,17 @@ def __getattr__(name: str) -> Any:
2829
"Install it with: pip install openai-agents[encrypt]"
2930
) from e
3031

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

0 commit comments

Comments
 (0)