chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:39:17 +08:00
commit 4ed4e9ff99
1368 changed files with 334957 additions and 0 deletions
+303
View File
@@ -0,0 +1,303 @@
# Advanced SQLite sessions
`AdvancedSQLiteSession` is an enhanced version of the basic `SQLiteSession` that provides advanced conversation management capabilities including conversation branching, detailed usage analytics, and structured conversation queries.
## Features
- **Conversation branching**: Create alternative conversation paths from any user message
- **Usage tracking**: Detailed token usage analytics per turn with full JSON breakdowns
- **Structured queries**: Get conversations by turns, tool usage statistics, and more
- **Branch management**: Independent branch switching and management
- **Message structure metadata**: Track message types, tool usage, and conversation flow
## Quick start
```python
from agents import Agent, Runner
from agents.extensions.memory import AdvancedSQLiteSession
# Create agent
agent = Agent(
name="Assistant",
instructions="Reply very concisely.",
)
# Create an advanced session
session = AdvancedSQLiteSession(
session_id="conversation_123",
db_path="conversations.db",
create_tables=True
)
# First conversation turn
result = await Runner.run(
agent,
"What city is the Golden Gate Bridge in?",
session=session
)
print(result.final_output) # "San Francisco"
# IMPORTANT: Store usage data
await session.store_run_usage(result)
# Continue conversation
result = await Runner.run(
agent,
"What state is it in?",
session=session
)
print(result.final_output) # "California"
await session.store_run_usage(result)
```
## Initialization
```python
from agents.extensions.memory import AdvancedSQLiteSession
# Basic initialization
session = AdvancedSQLiteSession(
session_id="my_conversation",
create_tables=True # Auto-create advanced tables
)
# With persistent storage
session = AdvancedSQLiteSession(
session_id="user_123",
db_path="path/to/conversations.db",
create_tables=True
)
# With custom logger
import logging
logger = logging.getLogger("my_app")
session = AdvancedSQLiteSession(
session_id="session_456",
create_tables=True,
logger=logger
)
```
### Parameters
- `session_id` (str): Unique identifier for the conversation session
- `db_path` (str | Path): Path to SQLite database file. Defaults to `:memory:` for in-memory storage
- `create_tables` (bool): Whether to automatically create the advanced tables. Defaults to `False`
- `logger` (logging.Logger | None): Custom logger for the session. Defaults to module logger
## Usage tracking
AdvancedSQLiteSession provides detailed usage analytics by storing token usage data per conversation turn. **This is entirely dependent on the `store_run_usage` method being called after each agent run.**
### Storing usage data
```python
# After each agent run, store the usage data
result = await Runner.run(agent, "Hello", session=session)
await session.store_run_usage(result)
# This stores:
# - Total tokens used
# - Input/output token breakdown
# - Request count
# - Detailed JSON token information (if available)
```
### Retrieving usage statistics
```python
# Get session-level usage (all branches)
session_usage = await session.get_session_usage()
if session_usage:
print(f"Total requests: {session_usage['requests']}")
print(f"Total tokens: {session_usage['total_tokens']}")
print(f"Input tokens: {session_usage['input_tokens']}")
print(f"Output tokens: {session_usage['output_tokens']}")
print(f"Total turns: {session_usage['total_turns']}")
# Get usage for specific branch
branch_usage = await session.get_session_usage(branch_id="main")
# Get usage by turn
turn_usage = await session.get_turn_usage()
for turn_data in turn_usage:
print(f"Turn {turn_data['user_turn_number']}: {turn_data['total_tokens']} tokens")
if turn_data['input_tokens_details']:
print(f" Input details: {turn_data['input_tokens_details']}")
if turn_data['output_tokens_details']:
print(f" Output details: {turn_data['output_tokens_details']}")
# Get usage for specific turn
turn_2_usage = await session.get_turn_usage(user_turn_number=2)
```
## Conversation branching
One of the key features of AdvancedSQLiteSession is the ability to create conversation branches from any user message, allowing you to explore alternative conversation paths.
### Creating branches
```python
# Get available turns for branching
turns = await session.get_conversation_turns()
for turn in turns:
print(f"Turn {turn['turn']}: {turn['content']}")
print(f"Can branch: {turn['can_branch']}")
# Create a branch from turn 2
branch_id = await session.create_branch_from_turn(2)
print(f"Created branch: {branch_id}")
# Create a branch with custom name
branch_id = await session.create_branch_from_turn(
2,
branch_name="alternative_path"
)
# Create branch by searching for content
branch_id = await session.create_branch_from_content(
"weather",
branch_name="weather_focus"
)
```
### Branch management
```python
# List all branches
branches = await session.list_branches()
for branch in branches:
current = " (current)" if branch["is_current"] else ""
print(f"{branch['branch_id']}: {branch['user_turns']} turns, {branch['message_count']} messages{current}")
# Switch between branches
await session.switch_to_branch("main")
await session.switch_to_branch(branch_id)
# Delete a branch
await session.delete_branch(branch_id, force=True) # force=True allows deleting current branch
```
### Branch workflow example
```python
# Original conversation
result = await Runner.run(agent, "What's the capital of France?", session=session)
await session.store_run_usage(result)
result = await Runner.run(agent, "What's the weather like there?", session=session)
await session.store_run_usage(result)
# Create branch from turn 2 (weather question)
branch_id = await session.create_branch_from_turn(2, "weather_focus")
# Continue in new branch with different question
result = await Runner.run(
agent,
"What are the main tourist attractions in Paris?",
session=session
)
await session.store_run_usage(result)
# Switch back to main branch
await session.switch_to_branch("main")
# Continue original conversation
result = await Runner.run(
agent,
"How expensive is it to visit?",
session=session
)
await session.store_run_usage(result)
```
## Structured queries
AdvancedSQLiteSession provides several methods for analyzing conversation structure and content.
### Conversation analysis
```python
# Get conversation organized by turns
conversation_by_turns = await session.get_conversation_by_turns()
for turn_num, items in conversation_by_turns.items():
print(f"Turn {turn_num}: {len(items)} items")
for item in items:
if item["tool_name"]:
print(f" - {item['type']} (tool: {item['tool_name']})")
else:
print(f" - {item['type']}")
# Get tool usage statistics
tool_usage = await session.get_tool_usage()
for tool_name, count, turn in tool_usage:
print(f"{tool_name}: used {count} times in turn {turn}")
# Find turns by content
matching_turns = await session.find_turns_by_content("weather")
for turn in matching_turns:
print(f"Turn {turn['turn']}: {turn['content']}")
```
### Message structure
The session automatically tracks message structure including:
- Message types (user, assistant, tool_call, etc.)
- Tool names for tool calls
- Turn numbers and sequence numbers
- Branch associations
- Timestamps
## Database schema
AdvancedSQLiteSession extends the basic SQLite schema with two additional tables:
### message_structure table
```sql
CREATE TABLE message_structure (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
message_id INTEGER NOT NULL,
branch_id TEXT NOT NULL DEFAULT 'main',
message_type TEXT NOT NULL,
sequence_number INTEGER NOT NULL,
user_turn_number INTEGER,
branch_turn_number INTEGER,
tool_name TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE,
FOREIGN KEY (message_id) REFERENCES agent_messages(id) ON DELETE CASCADE
);
```
### turn_usage table
```sql
CREATE TABLE turn_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
branch_id TEXT NOT NULL DEFAULT 'main',
user_turn_number INTEGER NOT NULL,
requests INTEGER DEFAULT 0,
input_tokens INTEGER DEFAULT 0,
output_tokens INTEGER DEFAULT 0,
total_tokens INTEGER DEFAULT 0,
input_tokens_details JSON,
output_tokens_details JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE,
UNIQUE(session_id, branch_id, user_turn_number)
);
```
## Complete example
Check out the [complete example](https://github.com/openai/openai-agents-python/tree/main/examples/memory/advanced_sqlite_session_example.py) for a comprehensive demonstration of all features.
## API reference
- [`AdvancedSQLiteSession`][agents.extensions.memory.advanced_sqlite_session.AdvancedSQLiteSession] - Main class
- [`Session`][agents.memory.session.Session] - Base session protocol
+175
View File
@@ -0,0 +1,175 @@
# Encrypted sessions
`EncryptedSession` provides transparent encryption for any session implementation, securing conversation data with automatic expiration of old items.
## Features
- **Transparent encryption**: Wraps any session with Fernet encryption
- **Per-session keys**: Uses HKDF key derivation for unique encryption per session
- **Automatic expiration**: Old items are silently skipped when TTL expires
- **Drop-in replacement**: Works with any existing session implementation
## Installation
Encrypted sessions require the `encrypt` extra:
```bash
pip install openai-agents[encrypt]
```
## Quick start
```python
import asyncio
from agents import Agent, Runner
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
async def main():
agent = Agent("Assistant")
# Create underlying session
underlying_session = SQLAlchemySession.from_url(
"user-123",
url="sqlite+aiosqlite:///:memory:",
create_tables=True
)
# Wrap with encryption
session = EncryptedSession(
session_id="user-123",
underlying_session=underlying_session,
encryption_key="your-secret-key-here",
ttl=600 # 10 minutes
)
result = await Runner.run(agent, "Hello", session=session)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
```
## Configuration
### Encryption key
The encryption key can be either a Fernet key or any string:
```python
from agents.extensions.memory import EncryptedSession
# Using a Fernet key (base64-encoded)
session = EncryptedSession(
session_id="user-123",
underlying_session=underlying_session,
encryption_key="your-fernet-key-here",
ttl=600
)
# Using a raw string (will be derived to a key)
session = EncryptedSession(
session_id="user-123",
underlying_session=underlying_session,
encryption_key="my-secret-password",
ttl=600
)
```
### TTL (time to live)
Set how long encrypted items remain valid:
```python
# Items expire after 1 hour
session = EncryptedSession(
session_id="user-123",
underlying_session=underlying_session,
encryption_key="secret",
ttl=3600 # 1 hour in seconds
)
# Items expire after 1 day
session = EncryptedSession(
session_id="user-123",
underlying_session=underlying_session,
encryption_key="secret",
ttl=86400 # 24 hours in seconds
)
```
## Usage with different session types
### With SQLite sessions
```python
from agents import SQLiteSession
from agents.extensions.memory import EncryptedSession
# Create encrypted SQLite session
underlying = SQLiteSession("user-123", "conversations.db")
session = EncryptedSession(
session_id="user-123",
underlying_session=underlying,
encryption_key="secret-key"
)
```
### With SQLAlchemy sessions
```python
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
# Create encrypted SQLAlchemy session
underlying = SQLAlchemySession.from_url(
"user-123",
url="postgresql+asyncpg://user:pass@localhost/db",
create_tables=True
)
session = EncryptedSession(
session_id="user-123",
underlying_session=underlying,
encryption_key="secret-key"
)
```
!!! warning "Advanced Session Features"
When using `EncryptedSession` with advanced session implementations like `AdvancedSQLiteSession`, note that:
- Methods like `find_turns_by_content()` won't work effectively since message content is encrypted
- Content-based searches operate on encrypted data, limiting their effectiveness
## Key derivation
EncryptedSession uses HKDF (HMAC-based Key Derivation Function) to derive unique encryption keys per session:
- **Master key**: Your provided encryption key
- **Session salt**: The session ID
- **Info string**: `"agents.session-store.hkdf.v1"`
- **Output**: 32-byte Fernet key
This ensures that:
- Each session has a unique encryption key
- Keys cannot be derived without the master key
- Session data cannot be decrypted across different sessions
## Automatic expiration
When items exceed the TTL, they are automatically skipped during retrieval:
```python
# Items older than TTL are silently ignored
items = await session.get_items() # Only returns non-expired items
# Expired items don't affect session behavior
result = await Runner.run(agent, "Continue conversation", session=session)
```
## API reference
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - Main class
- [`Session`][agents.memory.session.Session] - Base session protocol
+707
View File
@@ -0,0 +1,707 @@
# Sessions
The Agents SDK provides built-in session memory to automatically maintain conversation history across multiple agent runs, eliminating the need to manually handle `.to_input_list()` between turns.
Sessions stores conversation history for a specific session, allowing agents to maintain context without requiring explicit manual memory management. This is particularly useful for building chat applications or multi-turn conversations where you want the agent to remember previous interactions.
Use sessions when you want the SDK to manage client-side memory for you. Sessions cannot be combined with `conversation_id`, `previous_response_id`, or `auto_previous_response_id` in the same run. If you want OpenAI server-managed continuation instead, choose one of those mechanisms rather than layering a session on top.
## Quick start
```python
from agents import Agent, Runner, SQLiteSession
# Create agent
agent = Agent(
name="Assistant",
instructions="Reply very concisely.",
)
# Create a session instance with a session ID
session = SQLiteSession("conversation_123")
# First turn
result = await Runner.run(
agent,
"What city is the Golden Gate Bridge in?",
session=session
)
print(result.final_output) # "San Francisco"
# Second turn - agent automatically remembers previous context
result = await Runner.run(
agent,
"What state is it in?",
session=session
)
print(result.final_output) # "California"
# Also works with synchronous runner
result = Runner.run_sync(
agent,
"What's the population?",
session=session
)
print(result.final_output) # "Approximately 39 million"
```
## Resuming interrupted runs with the same session
If a run pauses for approval, resume it with the same session instance (or another session instance that points at the same backing store) so the resumed turn continues the same stored conversation history.
```python
result = await Runner.run(agent, "Delete temporary files that are no longer needed.", session=session)
if result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
state.approve(interruption)
result = await Runner.run(agent, state, session=session)
```
## Core session behavior
When session memory is enabled:
1. **Before each run**: The runner automatically retrieves the conversation history for the session and prepends it to the input items.
2. **After each run**: All new items generated during the run (user input, assistant responses, tool calls, etc.) are automatically stored in the session.
3. **Context preservation**: Each subsequent run with the same session includes the full conversation history, allowing the agent to maintain context.
This eliminates the need to manually call `.to_input_list()` and manage conversation state between runs.
## Control how history and new input merge
When you pass a session, the runner normally prepares model input as:
1. Session history (retrieved from `session.get_items(...)`)
2. New turn input
Use [`RunConfig.session_input_callback`][agents.run.RunConfig.session_input_callback] to customize that merge step before the model call. The callback receives two lists:
- `history`: The retrieved session history (already normalized into input-item format)
- `new_input`: The current turn's new input items
Return the final list of input items that should be sent to the model.
The callback receives copies of both lists, so you can safely mutate them. The returned list controls the model input for that turn, but the SDK still persists only items that belong to the new turn. Reordering or filtering old history therefore does not cause old session items to be saved again as fresh input.
```python
from agents import Agent, RunConfig, Runner, SQLiteSession
def keep_recent_history(history, new_input):
# Keep only the last 10 history items, then append the new turn.
return history[-10:] + new_input
agent = Agent(name="Assistant")
session = SQLiteSession("conversation_123")
result = await Runner.run(
agent,
"Continue from the latest updates only.",
session=session,
run_config=RunConfig(session_input_callback=keep_recent_history),
)
```
Use this when you need custom pruning, reordering, or selective inclusion of history without changing how the session stores items. If you need a later final pass immediately before the model call, use [`call_model_input_filter`][agents.run.RunConfig.call_model_input_filter] from the [running agents guide](../running_agents.md).
## Limiting retrieved history
Use [`SessionSettings`][agents.memory.SessionSettings] to control how much history is fetched before each run.
- `SessionSettings(limit=None)` (default): retrieve all available session items
- `SessionSettings(limit=N)`: retrieve only the most recent `N` items
You can apply this per run via [`RunConfig.session_settings`][agents.run.RunConfig.session_settings]:
```python
from agents import Agent, RunConfig, Runner, SessionSettings, SQLiteSession
agent = Agent(name="Assistant")
session = SQLiteSession("conversation_123")
result = await Runner.run(
agent,
"Summarize our recent discussion.",
session=session,
run_config=RunConfig(session_settings=SessionSettings(limit=50)),
)
```
If your session implementation exposes default session settings, `RunConfig.session_settings` overrides any non-`None` values for that run. This is useful for long conversations where you want to cap retrieval size without changing the session's default behavior.
## Memory operations
### Basic operations
Sessions supports several operations for managing conversation history:
```python
from agents import SQLiteSession
session = SQLiteSession("user_123", "conversations.db")
# 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
last_item = await session.pop_item()
print(last_item) # {"role": "assistant", "content": "Hi there!"}
# Clear all items from a session
await session.clear_session()
```
### Using pop_item for corrections
The `pop_item` method is particularly useful when you want to undo or modify the last item in a conversation:
```python
from agents import Agent, Runner, SQLiteSession
agent = Agent(name="Assistant")
session = SQLiteSession("correction_example")
# Initial conversation
result = await Runner.run(
agent,
"What's 2 + 2?",
session=session
)
print(f"Agent: {result.final_output}")
# User wants to correct their question
assistant_item = await session.pop_item() # Remove agent's response
user_item = await session.pop_item() # Remove user's question
# Ask a corrected question
result = await Runner.run(
agent,
"What's 2 + 3?",
session=session
)
print(f"Agent: {result.final_output}")
```
## Built-in session implementations
The SDK provides several session implementations for different use cases:
### Choose a built-in session implementation
Use this table to pick a starting point before reading the detailed examples below.
| Session type | Best for | Notes |
| --- | --- | --- |
| `SQLiteSession` | Local development and simple apps | Built-in, lightweight, file-backed or in-memory |
| `AsyncSQLiteSession` | Async SQLite with `aiosqlite` | Extension backend with async driver support |
| `RedisSession` | Shared memory across workers/services | Good for low-latency distributed deployments |
| `SQLAlchemySession` | Production apps with existing databases | Works with SQLAlchemy-supported databases |
| `MongoDBSession` | Apps already using MongoDB or needing multi-process storage | Async pymongo; atomic sequence counter for ordering |
| `DaprSession` | Cloud-native deployments with Dapr sidecars | Supports multiple state stores plus TTL and consistency controls |
| `OpenAIConversationsSession` | Server-managed storage in OpenAI | OpenAI Conversations API-backed history |
| `OpenAIResponsesCompactionSession` | Long conversations with automatic compaction | Wrapper around another session backend |
| `AdvancedSQLiteSession` | SQLite plus branching/analytics | Heavier feature set; see dedicated page |
| `EncryptedSession` | Encryption + TTL on top of another session | Wrapper; choose an underlying backend first |
Some implementations have dedicated pages with additional details; those are linked inline in their subsections.
If you are implementing a Python server for ChatKit, use a `chatkit.store.Store` implementation for ChatKit's thread and item persistence. Agents SDK sessions such as `SQLAlchemySession` manage SDK-side conversation history, but they are not a drop-in replacement for ChatKit's store. See the [`chatkit-python` guide on implementing your ChatKit data store](https://github.com/openai/chatkit-python/blob/main/docs/guides/respond-to-user-message.md#implement-your-chatkit-data-store).
### OpenAI Conversations API sessions
Use [OpenAI's Conversations API](https://platform.openai.com/docs/api-reference/conversations) through `OpenAIConversationsSession`.
```python
from agents import Agent, Runner, OpenAIConversationsSession
# Create agent
agent = Agent(
name="Assistant",
instructions="Reply very concisely.",
)
# Create a new conversation
session = OpenAIConversationsSession()
# Optionally resume a previous conversation by passing a conversation ID
# session = OpenAIConversationsSession(conversation_id="conv_123")
# Start conversation
result = await Runner.run(
agent,
"What city is the Golden Gate Bridge in?",
session=session
)
print(result.final_output) # "San Francisco"
# Continue the conversation
result = await Runner.run(
agent,
"What state is it in?",
session=session
)
print(result.final_output) # "California"
```
### OpenAI Responses compaction sessions
Use `OpenAIResponsesCompactionSession` to compact stored conversation history with the Responses API (`responses.compact`). It wraps an underlying session and can automatically compact after each turn based on `should_trigger_compaction`. Do not wrap `OpenAIConversationsSession` with it; those two features manage history in different ways.
#### Typical usage (auto-compaction)
```python
from agents import Agent, Runner, SQLiteSession
from agents.memory import OpenAIResponsesCompactionSession
underlying = SQLiteSession("conversation_123")
session = OpenAIResponsesCompactionSession(
session_id="conversation_123",
underlying_session=underlying,
)
agent = Agent(name="Assistant")
result = await Runner.run(agent, "Hello", session=session)
print(result.final_output)
```
By default, compaction runs after each turn once the candidate threshold is reached.
`compaction_mode="previous_response_id"` works best when you are already chaining turns with Responses API response IDs. `compaction_mode="input"` rebuilds the compaction request from the current session items instead, which is useful when the response chain is unavailable or you want the session contents to be the source of truth. The default `"auto"` chooses the safest available option.
If your agent runs with `ModelSettings(store=False)`, the Responses API does not retain the last response for later lookup. In that stateless setup, the default `"auto"` mode falls back to input-based compaction instead of relying on `previous_response_id`. See [`examples/memory/compaction_session_stateless_example.py`](https://github.com/openai/openai-agents-python/tree/main/examples/memory/compaction_session_stateless_example.py) for a complete example.
#### auto-compaction can block streaming
Compaction clears and rewrites the session history, so the SDK waits for compaction to finish before considering the run complete. In streaming mode, this means `run.stream_events()` can stay open for a few seconds after the last output token if compaction is heavy.
If you want low-latency streaming or fast turn-taking, disable auto-compaction and call `run_compaction()` yourself between turns (or during idle time). You can decide when to force compaction based on your own criteria.
```python
from agents import Agent, Runner, SQLiteSession
from agents.memory import OpenAIResponsesCompactionSession
underlying = SQLiteSession("conversation_123")
session = OpenAIResponsesCompactionSession(
session_id="conversation_123",
underlying_session=underlying,
# Disable triggering the auto compaction
should_trigger_compaction=lambda _: False,
)
agent = Agent(name="Assistant")
result = await Runner.run(agent, "Hello", session=session)
# Decide when to compact (e.g., on idle, every N turns, or size thresholds).
await session.run_compaction({"force": True})
```
### SQLite sessions
The default, lightweight session implementation using SQLite:
```python
from agents import SQLiteSession
# In-memory database (lost when process ends)
session = SQLiteSession("user_123")
# Persistent file-based database
session = SQLiteSession("user_123", "conversations.db")
# Use the session
result = await Runner.run(
agent,
"Hello",
session=session
)
```
### Async SQLite sessions
Use `AsyncSQLiteSession` when you want SQLite persistence backed by `aiosqlite`.
```bash
pip install aiosqlite
```
```python
from agents import Agent, Runner
from agents.extensions.memory import AsyncSQLiteSession
agent = Agent(name="Assistant")
session = AsyncSQLiteSession("user_123", db_path="conversations.db")
result = await Runner.run(agent, "Hello", session=session)
```
### Redis sessions
Use `RedisSession` for shared session memory across multiple workers or services.
```bash
pip install openai-agents[redis]
```
```python
from agents import Agent, Runner
from agents.extensions.memory import RedisSession
agent = Agent(name="Assistant")
session = RedisSession.from_url(
"user_123",
url="redis://localhost:6379/0",
)
result = await Runner.run(agent, "Hello", session=session)
```
### SQLAlchemy sessions
Production-ready Agents SDK session persistence using any SQLAlchemy-supported database:
```python
from agents.extensions.memory import SQLAlchemySession
# Using database URL
session = SQLAlchemySession.from_url(
"user_123",
url="postgresql+asyncpg://user:pass@localhost/db",
create_tables=True
)
# Using existing engine
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
session = SQLAlchemySession("user_123", engine=engine, create_tables=True)
```
See [SQLAlchemy Sessions](sqlalchemy_session.md) for detailed documentation.
### Dapr sessions
Use `DaprSession` when you already run Dapr sidecars or want session storage that can move across different state-store backends without changing your agent code.
```bash
pip install openai-agents[dapr]
```
```python
from agents import Agent, Runner
from agents.extensions.memory import DaprSession
agent = Agent(name="Assistant")
async with DaprSession.from_address(
"user_123",
state_store_name="statestore",
dapr_address="localhost:50001",
) as session:
result = await Runner.run(agent, "Hello", session=session)
print(result.final_output)
```
Notes:
- `from_address(...)` creates and owns the Dapr client for you. If your app already manages one, construct `DaprSession(...)` directly with `dapr_client=...`.
- Pass `ttl=...` to let the backing state store expire old session data automatically when the store supports TTL.
- Pass `consistency=DAPR_CONSISTENCY_STRONG` when you need stronger read-after-write guarantees.
- The Dapr Python SDK also checks the HTTP sidecar endpoint. In local development, start Dapr with `--dapr-http-port 3500` as well as the gRPC port used in `dapr_address`.
- See [`examples/memory/dapr_session_example.py`](https://github.com/openai/openai-agents-python/tree/main/examples/memory/dapr_session_example.py) for a full setup walkthrough, including local components and troubleshooting.
### MongoDB sessions
Use `MongoDBSession` for applications that already use MongoDB or need horizontally-scalable, multi-process session storage.
```bash
pip install openai-agents[mongodb]
```
```python
from agents import Agent, Runner
from agents.extensions.memory import MongoDBSession
agent = Agent(name="Assistant")
# Create from URI — owns the client and closes it when session.close() is called
session = MongoDBSession.from_uri(
"user-123",
uri="mongodb://localhost:27017",
database="agents",
)
result = await Runner.run(agent, "Hello", session=session)
print(result.final_output)
await session.close()
```
Notes:
- `from_uri(...)` creates and owns the `AsyncMongoClient` and closes it on `session.close()`. If your application already manages a client, construct `MongoDBSession(...)` directly with `client=...`; in that case `session.close()` is a no-op and lifecycle stays with the caller.
- Connect to [MongoDB Atlas](https://www.mongodb.com/products/platform) by passing an `mongodb+srv://user:password@cluster.example.mongodb.net` URI to `from_uri(...)` with no other changes.
- Two collections are used and both names are configurable via `sessions_collection=` (default `agent_sessions`) and `messages_collection=` (default `agent_messages`). Indexes are created automatically on first use. Each message document carries a monotonically increasing `seq` counter that preserves ordering across concurrent writers and processes.
- Use `await session.ping()` to verify connectivity before your first run.
### Advanced SQLite sessions
Enhanced SQLite sessions with conversation branching, usage analytics, and structured queries:
```python
from agents.extensions.memory import AdvancedSQLiteSession
# Create with advanced features
session = AdvancedSQLiteSession(
session_id="user_123",
db_path="conversations.db",
create_tables=True
)
# Automatic usage tracking
result = await Runner.run(agent, "Hello", session=session)
await session.store_run_usage(result) # Track token usage
# Conversation branching
await session.create_branch_from_turn(2) # Branch from turn 2
```
See [Advanced SQLite Sessions](advanced_sqlite_session.md) for detailed documentation.
### Encrypted sessions
Transparent encryption wrapper for any session implementation:
```python
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
# Create underlying session
underlying_session = SQLAlchemySession.from_url(
"user_123",
url="sqlite+aiosqlite:///conversations.db",
create_tables=True
)
# Wrap with encryption and TTL
session = EncryptedSession(
session_id="user_123",
underlying_session=underlying_session,
encryption_key="your-secret-key",
ttl=600 # 10 minutes
)
result = await Runner.run(agent, "Hello", session=session)
```
See [Encrypted Sessions](encrypted_session.md) for detailed documentation.
### Other session types
There are a few more built-in options. Please refer to `examples/memory/` and source code under `extensions/memory/`.
## Operational patterns
### Session ID naming
Use meaningful session IDs that help you organize conversations:
- User-based: `"user_12345"`
- Thread-based: `"thread_abc123"`
- Context-based: `"support_ticket_456"`
### Memory persistence
- Use in-memory SQLite (`SQLiteSession("session_id")`) for temporary conversations
- Use file-based SQLite (`SQLiteSession("session_id", "path/to/db.sqlite")`) for persistent conversations
- Use async SQLite (`AsyncSQLiteSession("session_id", db_path="...")`) when you need an `aiosqlite`-based implementation
- Use Redis-backed sessions (`RedisSession.from_url("session_id", url="redis://...")`) for shared, low-latency session memory
- Use SQLAlchemy-powered sessions (`SQLAlchemySession("session_id", engine=engine, create_tables=True)`) for production systems with existing databases supported by SQLAlchemy
- Use MongoDB sessions (`MongoDBSession.from_uri("session_id", uri="mongodb://localhost:27017")`) for applications already using MongoDB or needing multi-process, horizontally-scalable session storage
- Use Dapr state store sessions (`DaprSession.from_address("session_id", state_store_name="statestore", dapr_address="localhost:50001")`) for production cloud-native deployments with support for 30+ database backends with built-in telemetry, tracing, and data isolation
- Use OpenAI-hosted storage (`OpenAIConversationsSession()`) when you prefer to store history in the OpenAI Conversations API
- Use encrypted sessions (`EncryptedSession(session_id, underlying_session, encryption_key)`) to wrap any session with transparent encryption and TTL-based expiration
- Consider implementing custom session backends for other production systems (for example, Django) for more advanced use cases
### Multiple sessions
```python
from agents import Agent, Runner, SQLiteSession
agent = Agent(name="Assistant")
# Different sessions maintain separate conversation histories
session_1 = SQLiteSession("user_123", "conversations.db")
session_2 = SQLiteSession("user_456", "conversations.db")
result1 = await Runner.run(
agent,
"Help me with my account",
session=session_1
)
result2 = await Runner.run(
agent,
"What are my charges?",
session=session_2
)
```
### Session sharing
```python
# Different agents can share the same session
support_agent = Agent(name="Support")
billing_agent = Agent(name="Billing")
session = SQLiteSession("user_123")
# Both agents will see the same conversation history
result1 = await Runner.run(
support_agent,
"Help me with my account",
session=session
)
result2 = await Runner.run(
billing_agent,
"What are my charges?",
session=session
)
```
## Complete example
Here's a complete example showing session memory in action:
```python
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 = SQLiteSession("conversation_123", "conversation_history.db")
print("=== Sessions 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.")
if __name__ == "__main__":
asyncio.run(main())
```
## Custom session implementations
You can implement your own session memory by creating a class that follows the [`Session`][agents.memory.session.Session] protocol:
```python
from agents.memory.session import SessionABC
from agents.items import TResponseInputItem
from typing import List
class MyCustomSession(SessionABC):
"""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[TResponseInputItem]:
"""Retrieve conversation history for this session."""
# Your implementation here
pass
async def add_items(self, items: List[TResponseInputItem]) -> None:
"""Store new items for this session."""
# Your implementation here
pass
async def pop_item(self) -> TResponseInputItem | 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
# Use your custom session
agent = Agent(name="Assistant")
result = await Runner.run(
agent,
"Hello",
session=MyCustomSession("my_session")
)
```
## Community session implementations
The community has developed additional session implementations:
| Package | Description |
|---------|-------------|
| [openai-django-sessions](https://pypi.org/project/openai-django-sessions/) | Django ORM-based sessions for any Django-supported database (PostgreSQL, MySQL, SQLite, and more) |
If you've built a session implementation, please feel free to submit a documentation PR to add it here!
## API reference
For detailed API documentation, see:
- [`Session`][agents.memory.session.Session] - Protocol interface
- [`OpenAIConversationsSession`][agents.memory.OpenAIConversationsSession] - OpenAI Conversations API implementation
- [`OpenAIResponsesCompactionSession`][agents.memory.openai_responses_compaction_session.OpenAIResponsesCompactionSession] - Responses API compaction wrapper
- [`SQLiteSession`][agents.memory.sqlite_session.SQLiteSession] - Basic SQLite implementation
- [`AsyncSQLiteSession`][agents.extensions.memory.async_sqlite_session.AsyncSQLiteSession] - Async SQLite implementation based on `aiosqlite`
- [`RedisSession`][agents.extensions.memory.redis_session.RedisSession] - Redis-backed session implementation
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - SQLAlchemy-powered implementation
- [`MongoDBSession`][agents.extensions.memory.mongodb_session.MongoDBSession] - MongoDB-backed session implementation
- [`DaprSession`][agents.extensions.memory.dapr_session.DaprSession] - Dapr state store implementation
- [`AdvancedSQLiteSession`][agents.extensions.memory.advanced_sqlite_session.AdvancedSQLiteSession] - Enhanced SQLite with branching and analytics
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - Encrypted wrapper for any session
+76
View File
@@ -0,0 +1,76 @@
# SQLAlchemy sessions
`SQLAlchemySession` uses SQLAlchemy to provide a production-ready session implementation, allowing you to use any database supported by SQLAlchemy (PostgreSQL, MySQL, SQLite, etc.) for session storage.
## Installation
SQLAlchemy sessions require the `sqlalchemy` extra:
```bash
pip install openai-agents[sqlalchemy]
```
## Quick start
### Using database URL
The simplest way to get started:
```python
import asyncio
from agents import Agent, Runner
from agents.extensions.memory import SQLAlchemySession
async def main():
agent = Agent("Assistant")
# Create session using database URL
session = SQLAlchemySession.from_url(
"user-123",
url="sqlite+aiosqlite:///:memory:",
create_tables=True
)
result = await Runner.run(agent, "Hello", session=session)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
```
### Using existing engine
For applications with existing SQLAlchemy engines:
```python
import asyncio
from agents import Agent, Runner
from agents.extensions.memory import SQLAlchemySession
from sqlalchemy.ext.asyncio import create_async_engine
async def main():
# Create your database engine
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
agent = Agent("Assistant")
session = SQLAlchemySession(
"user-456",
engine=engine,
create_tables=True
)
result = await Runner.run(agent, "Hello", session=session)
print(result.final_output)
# Clean up
await engine.dispose()
if __name__ == "__main__":
asyncio.run(main())
```
## API reference
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - Main class
- [`Session`][agents.memory.session.Session] - Base session protocol