From f2d0bd1640508e9eab09bda085ddc54f37a08ed2 Mon Sep 17 00:00:00 2001 From: habema Date: Tue, 23 Sep 2025 18:05:24 +0300 Subject: [PATCH 1/4] Introduce docs for `AdvancedSQLiteSession` and refactor sessions section in docs --- .../memory/advanced_sqlite_session.md | 3 + .../extensions/memory/encrypted_session.md | 3 + docs/ref/memory.md | 1 + docs/sessions/advanced_sqlite_session.md | 303 ++++++++++++++++++ docs/sessions/encrypted_session.md | 175 ++++++++++ docs/{sessions.md => sessions/index.md} | 276 ++++++++-------- docs/sessions/sqlalchemy_session.md | 76 +++++ mkdocs.yml | 15 +- 8 files changed, 724 insertions(+), 128 deletions(-) create mode 100644 docs/ref/extensions/memory/advanced_sqlite_session.md create mode 100644 docs/ref/extensions/memory/encrypted_session.md create mode 100644 docs/sessions/advanced_sqlite_session.md create mode 100644 docs/sessions/encrypted_session.md rename docs/{sessions.md => sessions/index.md} (72%) create mode 100644 docs/sessions/sqlalchemy_session.md diff --git a/docs/ref/extensions/memory/advanced_sqlite_session.md b/docs/ref/extensions/memory/advanced_sqlite_session.md new file mode 100644 index 000000000..74feeb553 --- /dev/null +++ b/docs/ref/extensions/memory/advanced_sqlite_session.md @@ -0,0 +1,3 @@ +# `AdvancedSQLiteSession` + +::: agents.extensions.memory.advanced_sqlite_session.AdvancedSQLiteSession diff --git a/docs/ref/extensions/memory/encrypted_session.md b/docs/ref/extensions/memory/encrypted_session.md new file mode 100644 index 000000000..0bfacd99d --- /dev/null +++ b/docs/ref/extensions/memory/encrypted_session.md @@ -0,0 +1,3 @@ +# `EncryptedSession` + +::: agents.extensions.memory.encrypt_session.EncryptedSession diff --git a/docs/ref/memory.md b/docs/ref/memory.md index 04a2258bf..eb78a51a5 100644 --- a/docs/ref/memory.md +++ b/docs/ref/memory.md @@ -6,3 +6,4 @@ members: - Session - SQLiteSession + - OpenAIConversationsSession diff --git a/docs/sessions/advanced_sqlite_session.md b/docs/sessions/advanced_sqlite_session.md new file mode 100644 index 000000000..ab6bfa5d8 --- /dev/null +++ b/docs/sessions/advanced_sqlite_session.md @@ -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 diff --git a/docs/sessions/encrypted_session.md b/docs/sessions/encrypted_session.md new file mode 100644 index 000000000..ba3755ae9 --- /dev/null +++ b/docs/sessions/encrypted_session.md @@ -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 diff --git a/docs/sessions.md b/docs/sessions/index.md similarity index 72% rename from docs/sessions.md rename to docs/sessions/index.md index 88b5f1054..ebefbac82 100644 --- a/docs/sessions.md +++ b/docs/sessions/index.md @@ -113,37 +113,13 @@ result = await Runner.run( print(f"Agent: {result.final_output}") ``` -## Memory options +## Session types -### No memory (default) +The SDK provides several session implementations for different use cases: -```python -# Default behavior - no session memory -result = await Runner.run(agent, "Hello") -``` - -### OpenAI Conversations API memory - -Use the [OpenAI Conversations API](https://platform.openai.com/docs/guides/conversational-agents/conversations-api) to persist -conversation state without managing your own database. This is helpful when you already rely on OpenAI-hosted infrastructure -for storing conversation history. - -```python -from agents import OpenAIConversationsSession - -session = OpenAIConversationsSession() - -# Optionally resume a previous conversation by passing a conversation ID -# session = OpenAIConversationsSession(conversation_id="conv_123") +### SQLite sessions -result = await Runner.run( - agent, - "Hello", - session=session, -) -``` - -### SQLite memory +The default, lightweight session implementation using SQLite: ```python from agents import SQLiteSession @@ -162,132 +138,115 @@ result = await Runner.run( ) ``` -### Multiple sessions +### 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, SQLiteSession +from agents import Agent, Runner, OpenAIConversationsSession -agent = Agent(name="Assistant") +# Create agent +agent = Agent( + name="Assistant", + instructions="Reply very concisely.", +) -# Different sessions maintain separate conversation histories -session_1 = SQLiteSession("user_123", "conversations.db") -session_2 = SQLiteSession("user_456", "conversations.db") +# Create a new conversation +session = OpenAIConversationsSession() -result1 = await Runner.run( +# Optionally resume a previous conversation by passing a conversation ID +# session = OpenAIConversationsSession(conversation_id="conv_123") + +# Start conversation +result = await Runner.run( agent, - "Hello", - session=session_1 + "What city is the Golden Gate Bridge in?", + session=session ) -result2 = await Runner.run( +print(result.final_output) # "San Francisco" + +# Continue the conversation +result = await Runner.run( agent, - "Hello", - session=session_2 + "What state is it in?", + session=session ) +print(result.final_output) # "California" ``` -### SQLAlchemy-powered sessions - -For more advanced use cases, you can use a SQLAlchemy-powered session backend. This allows you to use any database supported by SQLAlchemy (PostgreSQL, MySQL, SQLite, etc.) for session storage. - -**Example 1: Using `from_url` with in-memory SQLite** +### SQLAlchemy sessions -This is the simplest way to get started, ideal for development and testing. +Production-ready sessions using any SQLAlchemy-supported database: ```python -import asyncio -from agents import Agent, Runner -from agents.extensions.memory.sqlalchemy_session import SQLAlchemySession +from agents.extensions.memory import SQLAlchemySession -async def main(): - agent = Agent("Assistant") - session = SQLAlchemySession.from_url( - "user-123", - url="sqlite+aiosqlite:///:memory:", - create_tables=True, # Auto-create tables for the demo - ) - - result = await Runner.run(agent, "Hello", session=session) +# Using database URL +session = SQLAlchemySession.from_url( + "user_123", + url="postgresql+asyncpg://user:pass@localhost/db", + create_tables=True +) -if __name__ == "__main__": - asyncio.run(main()) +# 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) ``` -**Example 2: Using an existing SQLAlchemy engine** +See [SQLAlchemy Sessions](sqlalchemy_session.md) for detailed documentation. -In a production application, you likely already have a SQLAlchemy `AsyncEngine` instance. You can pass it directly to the session. +### Advanced SQLite sessions -```python -import asyncio -from agents import Agent, Runner -from agents.extensions.memory.sqlalchemy_session import SQLAlchemySession -from sqlalchemy.ext.asyncio import create_async_engine +Enhanced SQLite sessions with conversation branching, usage analytics, and structured queries: -async def main(): - # In your application, you would use your existing engine - engine = create_async_engine("sqlite+aiosqlite:///conversations.db") - - agent = Agent("Assistant") - session = SQLAlchemySession( - "user-456", - engine=engine, - create_tables=True, # Auto-create tables for the demo - ) +```python +from agents.extensions.memory import AdvancedSQLiteSession - result = await Runner.run(agent, "Hello", session=session) - print(result.final_output) +# Create with advanced features +session = AdvancedSQLiteSession( + session_id="user_123", + db_path="conversations.db", + create_tables=True +) - await engine.dispose() +# Automatic usage tracking +result = await Runner.run(agent, "Hello", session=session) +await session.store_run_usage(result) # Track token usage -if __name__ == "__main__": - asyncio.run(main()) +# Conversation branching +await session.create_branch_from_turn(2) # Branch from turn 2 ``` +See [Advanced SQLite Sessions](advanced_sqlite_session.md) for detailed documentation. -## Custom memory implementations +### Encrypted sessions -You can implement your own session memory by creating a class that follows the [`Session`][agents.memory.session.Session] protocol: +Transparent encryption wrapper for any session implementation: ```python -from agents.memory.session import SessionABC -from agents.items import TResponseInputItem -from typing import List +from agents.extensions.memory import EncryptedSession, SQLAlchemySession -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 +# Create underlying session +underlying_session = SQLAlchemySession.from_url( + "user_123", + url="sqlite+aiosqlite:///conversations.db", + create_tables=True +) -# Use your custom session -agent = Agent(name="Assistant") -result = await Runner.run( - agent, - "Hello", - session=MyCustomSession("my_session") +# 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. + ## Session management ### Session ID naming @@ -306,12 +265,32 @@ Use meaningful session IDs that help you organize conversations: - Use OpenAI-hosted storage (`OpenAIConversationsSession()`) when you prefer to store history in the OpenAI Conversations API - Consider implementing custom session backends for other production systems (Redis, Django, etc.) for more advanced use cases -### Session management +### Multiple sessions ```python -# Clear a session when conversation should start fresh -await session.clear_session() +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") @@ -394,11 +373,58 @@ 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") +) +``` + ## API Reference For detailed API documentation, see: -- [`Session`][agents.memory.Session] - Protocol interface -- [`SQLiteSession`][agents.memory.SQLiteSession] - SQLite implementation -- [`OpenAIConversationsSession`](ref/memory/openai_conversations_session.md) - OpenAI Conversations API implementation +- [`Session`][agents.memory.session.Session] - Protocol interface +- [`SQLiteSession`][agents.memory.sqlite_session.SQLiteSession] - Basic SQLite implementation +- [`OpenAIConversationsSession`][agents.memory.OpenAIConversationsSession] - OpenAI Conversations API implementation - [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - SQLAlchemy-powered 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 diff --git a/docs/sessions/sqlalchemy_session.md b/docs/sessions/sqlalchemy_session.md new file mode 100644 index 000000000..c33cd6a34 --- /dev/null +++ b/docs/sessions/sqlalchemy_session.md @@ -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 diff --git a/mkdocs.yml b/mkdocs.yml index bea747bed..e62860fcb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,7 +57,11 @@ plugins: - Documentation: - agents.md - running_agents.md - - sessions.md + - Sessions: + - sessions/index.md + - sessions/sqlalchemy_session.md + - sessions/encrypted_session.md + - sessions/advanced_sqlite_session.md - results.md - streaming.md - repl.md @@ -145,7 +149,8 @@ plugins: - ref/extensions/handoff_prompt.md - ref/extensions/litellm.md - ref/extensions/memory/sqlalchemy_session.md - + - ref/extensions/memory/encrypted_session.md + - ref/extensions/memory/advanced_sqlite_session.md - locale: ja name: 日本語 build: true @@ -156,7 +161,11 @@ plugins: - ドキュメント: - agents.md - running_agents.md - - sessions.md + - Sessions: + - sessions/index.md + - sessions/sqlalchemy_session.md + - sessions/encrypted_session.md + - sessions/advanced_sqlite_session.md - results.md - streaming.md - repl.md From 9fc589107133fff0613658083361534a14cad30c Mon Sep 17 00:00:00 2001 From: habema Date: Tue, 23 Sep 2025 18:14:02 +0300 Subject: [PATCH 2/4] fix references --- docs/running_agents.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/running_agents.md b/docs/running_agents.md index e51b109cf..682a36a1a 100644 --- a/docs/running_agents.md +++ b/docs/running_agents.md @@ -89,7 +89,7 @@ async def main(): ### Automatic conversation management with Sessions -For a simpler approach, you can use [Sessions](sessions.md) to automatically handle conversation history without manually calling `.to_input_list()`: +For a simpler approach, you can use [Sessions](sessions/index.md) to automatically handle conversation history without manually calling `.to_input_list()`: ```python from agents import Agent, Runner, SQLiteSession @@ -119,7 +119,7 @@ Sessions automatically: - Stores new messages after each run - Maintains separate conversations for different session IDs -See the [Sessions documentation](sessions.md) for more details. +See the [Sessions documentation](sessions/index.md) for more details. ## Long running agents & human-in-the-loop From 961cbd7bb848609556c9c4308cb5f52fa0922678 Mon Sep 17 00:00:00 2001 From: habema Date: Thu, 2 Oct 2025 14:05:00 +0300 Subject: [PATCH 3/4] reorder sessions --- docs/sessions/index.md | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/sessions/index.md b/docs/sessions/index.md index ebefbac82..d703f6b22 100644 --- a/docs/sessions/index.md +++ b/docs/sessions/index.md @@ -117,27 +117,6 @@ print(f"Agent: {result.final_output}") The SDK provides several session implementations for different use cases: -### 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 -) -``` - ### OpenAI Conversations API sessions Use [OpenAI's Conversations API](https://platform.openai.com/docs/api-reference/conversations) through `OpenAIConversationsSession`. @@ -174,6 +153,27 @@ result = await Runner.run( print(result.final_output) # "California" ``` +### 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 +) +``` + ### SQLAlchemy sessions Production-ready sessions using any SQLAlchemy-supported database: @@ -423,8 +423,8 @@ result = await Runner.run( For detailed API documentation, see: - [`Session`][agents.memory.session.Session] - Protocol interface -- [`SQLiteSession`][agents.memory.sqlite_session.SQLiteSession] - Basic SQLite implementation - [`OpenAIConversationsSession`][agents.memory.OpenAIConversationsSession] - OpenAI Conversations API implementation +- [`SQLiteSession`][agents.memory.sqlite_session.SQLiteSession] - Basic SQLite implementation - [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - SQLAlchemy-powered 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 From 00eda6d9290e04eddc11ce97a1a0aa6f760b6421 Mon Sep 17 00:00:00 2001 From: habema Date: Thu, 9 Oct 2025 12:47:00 +0300 Subject: [PATCH 4/4] reorder session documentation --- mkdocs.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 7267d4c45..8ef27c8a1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -60,8 +60,8 @@ plugins: - Sessions: - sessions/index.md - sessions/sqlalchemy_session.md - - sessions/encrypted_session.md - sessions/advanced_sqlite_session.md + - sessions/encrypted_session.md - results.md - streaming.md - repl.md @@ -164,8 +164,8 @@ plugins: - Sessions: - sessions/index.md - sessions/sqlalchemy_session.md - - sessions/encrypted_session.md - sessions/advanced_sqlite_session.md + - sessions/encrypted_session.md - results.md - streaming.md - repl.md @@ -200,7 +200,11 @@ plugins: - 문서: - agents.md - running_agents.md - - sessions.md + - Sessions: + - sessions/index.md + - sessions/sqlalchemy_session.md + - sessions/advanced_sqlite_session.md + - sessions/encrypted_session.md - results.md - streaming.md - repl.md