6b94ad0f85
# Overview Resolves #745 This PR introduces **Sessions**, a new core feature that automatically maintains conversation history across multiple agent runs, eliminating the need to manually handle `.to_input_list()` between turns. ## Key Features ### 🧠 Automatic Memory Management - **Zero-effort conversation continuity**: Agents automatically remember previous context without manual state management - **Session-based organization**: Each conversation is isolated by unique session IDs - **Seamless integration**: Works with existing `Runner.run()`, `Runner.run_sync()`, and `Runner.run_streamed()` methods ### 🔌 Extensible Session Protocol - **Library-agnostic design**: Clean protocol interface allows any storage backend - **Drop-in implementations**: Easy integration with Redis, PostgreSQL, MongoDB, or any custom storage - **Production-ready interface**: Async-first design with proper error handling and type safety - **Vendor flexibility**: Library authors can provide their own Session implementations ### 💾 Built-in SQLite Implementation - **In-memory SQLite**: Perfect for temporary conversations during development - **Persistent SQLite**: File-based storage for conversations that survive application restarts - **Thread-safe operations**: Production-ready with connection pooling and proper concurrency handling ### 🔧 Simple API ```python # Before: Manual conversation management result1 = await Runner.run(agent, "What's the weather?") new_input = result1.to_input_list() + [{"role": "user", "content": "How about tomorrow?"}] result2 = await Runner.run(agent, new_input) # After: Automatic with Sessions session = SQLiteSession("user_123") result1 = await Runner.run(agent, "What's the weather?", session=session) result2 = await Runner.run(agent, "How about tomorrow?", session=session) # Remembers context automatically ``` ## What's Included ### Core Session Protocol - **`Session` Protocol**: Clean, async interface that any storage backend can implement - **Type-safe design**: Full type hints and runtime validation - **Standard operations**: `get_items()`, `add_items()`, `pop_item()`, `clear_session()` - **Extensibility-first**: Designed for third-party implementations ### Reference Implementation - **`SQLiteSession` Class**: Production-ready SQLite implementation - **Automatic schema management**: Creates tables and indexes automatically - **Connection pooling**: Thread-safe operations with proper resource management - **Flexible storage**: In-memory or persistent file-based databases ### Runner Integration - **New `session` parameter**: Drop-in addition to existing `Runner` methods - **Backward compatibility**: Zero breaking changes to existing code - **Automatic history management**: Prepends conversation history before each run ## Session Protocol for Library Authors The Session protocol provides a clean interface for implementing custom storage backends: ```python from agents.memory import Session from typing import List class MyCustomSession: """Custom session implementation following the Session protocol.""" def __init__(self, session_id: str): self.session_id = session_id # Your initialization here async def get_items(self, limit: int | None = None) -> List[dict]: """Retrieve conversation history for this session.""" # Your implementation here pass async def add_items(self, items: List[dict]) -> None: """Store new items for this session.""" # Your implementation here pass async def pop_item(self) -> dict | None: """Remove and return the most recent item from this session.""" # Your implementation here pass async def clear_session(self) -> None: """Clear all items for this session.""" # Your implementation here pass # Works seamlessly with any custom implementation result = await Runner.run(agent, "Hello", session=MyCustomSession("session_123")) ``` ### Example Third-Party Implementations ```python # Redis-based session (hypothetical library implementation) from redis_sessions import RedisSession session = RedisSession("user_123", redis_url="redis://localhost:6379") # PostgreSQL-based session (hypothetical library implementation) from postgres_sessions import PostgreSQLSession session = PostgreSQLSession("user_123", connection_string="postgresql://...") # Cloud-based session (hypothetical library implementation) from cloud_sessions import CloudSession session = CloudSession("user_123", api_key="...", region="us-east-1") # All work identically with the Runner result = await Runner.run(agent, "Hello", session=session) ``` ## Benefits ### For Application Developers - **Reduces boilerplate**: No more manual `.to_input_list()` management - **Prevents memory leaks**: Automatic cleanup and organized storage - **Easier debugging**: Clear conversation history tracking - **Flexible storage**: Choose the right backend for your needs ### For Library Authors - **Clean integration**: Simple protocol to implement for any storage backend - **Type safety**: Full type hints and runtime validation - **Async-first**: Modern async/await design throughout - **Documentation**: Comprehensive examples and API reference ### For Applications - **Better user experience**: Seamless conversation continuity - **Scalable architecture**: Support for multiple concurrent conversations - **Flexible deployment**: In-memory for development, production storage for scale - **Multi-agent support**: Same conversation history can be shared across different agents ## Usage Examples ### Basic Usage with SQLiteSession ```python from agents import Agent, Runner, SQLiteSession agent = Agent(name="Assistant", instructions="Reply concisely.") session = SQLiteSession("conversation_123") # Conversation flows naturally await Runner.run(agent, "Hi, I'm planning a trip to Japan", session=session) await Runner.run(agent, "What's the best time to visit?", session=session) await Runner.run(agent, "How about cherry blossom season?", session=session) ``` ### Multiple Sessions with Isolation ```python # Different users get separate conversation histories session_alice = SQLiteSession("user_alice") session_bob = SQLiteSession("user_bob") # Completely isolated conversations await Runner.run(agent, "I like pizza", session=session_alice) await Runner.run(agent, "I like sushi", session=session_bob) ``` ### Persistent vs In-Memory Storage ```python # In-memory database (lost when process ends) session = SQLiteSession("user_123") # Persistent file-based database session = SQLiteSession("user_123", "conversations.db") ``` ### Session Management Operations ```python session = SQLiteSession("user_123") # Get all items in a session items = await session.get_items() # Add new items to a session new_items = [ {"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi there!"} ] await session.add_items(new_items) # Remove and return the most recent item (useful for corrections) last_item = await session.pop_item() # Clear all items from a session await session.clear_session() ``` ### Message Correction Pattern ```python # User wants to correct their last question user_message = await session.pop_item() # Remove user's question assistant_message = await session.pop_item() # Remove agent's response # Ask a corrected question result = await Runner.run( agent, "What's 2 + 3?", # Corrected question session=session ) ``` ## Technical Details ### Session Protocol Design - **Async-first**: All operations are async for non-blocking I/O - **Type-safe**: Full type hints with runtime validation - **Error handling**: Graceful degradation and detailed error messages - **Resource management**: Proper cleanup and connection handling ### SQLiteSession Implementation - **Thread-safe operations** with connection pooling - **Automatic schema management** with proper indexing - **JSON serialization** for message storage - **Memory-efficient** conversation retrieval and storage - **Cross-platform compatibility** ## Breaking Changes None. This is a purely additive feature that doesn't affect existing functionality. ## Documentation - Updated core concepts in `docs/index.md` to highlight Sessions as a key primitive - New comprehensive guide at `docs/sessions.md` with protocol implementation examples - Enhanced `docs/running_agents.md` with automatic vs manual conversation management - Full API reference integration via `docs/ref/memory.md` - Implementation guide for library authors Sessions represent a significant architectural improvement for building conversational AI applications with the Agents SDK. The extensible Session protocol enables the ecosystem to provide specialized storage backends while maintaining a consistent, simple API for application developers. --------- Co-authored-by: Rohan Mehta <rm@openai.com>
78 lines
2.3 KiB
Python
78 lines
2.3 KiB
Python
"""
|
|
Example demonstrating session memory functionality.
|
|
|
|
This example shows how to use session memory to maintain conversation history
|
|
across multiple agent runs without manually handling .to_input_list().
|
|
"""
|
|
|
|
import asyncio
|
|
|
|
from agents import Agent, Runner, SQLiteSession
|
|
|
|
|
|
async def main():
|
|
# Create an agent
|
|
agent = Agent(
|
|
name="Assistant",
|
|
instructions="Reply very concisely.",
|
|
)
|
|
|
|
# Create a session instance that will persist across runs
|
|
session_id = "conversation_123"
|
|
session = SQLiteSession(session_id)
|
|
|
|
print("=== Session Example ===")
|
|
print("The agent will remember previous messages automatically.\n")
|
|
|
|
# First turn
|
|
print("First turn:")
|
|
print("User: What city is the Golden Gate Bridge in?")
|
|
result = await Runner.run(
|
|
agent,
|
|
"What city is the Golden Gate Bridge in?",
|
|
session=session,
|
|
)
|
|
print(f"Assistant: {result.final_output}")
|
|
print()
|
|
|
|
# Second turn - the agent will remember the previous conversation
|
|
print("Second turn:")
|
|
print("User: What state is it in?")
|
|
result = await Runner.run(agent, "What state is it in?", session=session)
|
|
print(f"Assistant: {result.final_output}")
|
|
print()
|
|
|
|
# Third turn - continuing the conversation
|
|
print("Third turn:")
|
|
print("User: What's the population of that state?")
|
|
result = await Runner.run(
|
|
agent,
|
|
"What's the population of that state?",
|
|
session=session,
|
|
)
|
|
print(f"Assistant: {result.final_output}")
|
|
print()
|
|
|
|
print("=== Conversation Complete ===")
|
|
print("Notice how the agent remembered the context from previous turns!")
|
|
print("Sessions automatically handles conversation history.")
|
|
|
|
# Demonstrate the limit parameter - get only the latest 2 items
|
|
print("\n=== Latest Items Demo ===")
|
|
latest_items = await session.get_items(limit=2)
|
|
print("Latest 2 items:")
|
|
for i, msg in enumerate(latest_items, 1):
|
|
role = msg.get("role", "unknown")
|
|
content = msg.get("content", "")
|
|
print(f" {i}. {role}: {content}")
|
|
|
|
print(f"\nFetched {len(latest_items)} out of total conversation history.")
|
|
|
|
# Get all items to show the difference
|
|
all_items = await session.get_items()
|
|
print(f"Total items in session: {len(all_items)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|