-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Feat: Add AdvancedSQLiteSession with conversation branching & usage tracking #1662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+2,560
−0
Merged
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
b3ed91f
feat: StructuredSQLiteSession
habema 86b069a
fix: Skip tests if SQLAlchemy in old_versions_test
habema 474b0b7
rename to AdvancedSQLiteSession
habema 3a24195
refactor: switch temporary local db to in-memory in test
habema 816c728
Merge branch 'main' into session-schema-improvements
habema 8496760
Merge branch 'main' into session-schema-improvements
habema b722427
Merge branch 'main' into session-schema-improvements
seratch 3e49696
Update src/agents/extensions/memory/advanced_sqlite_session.py
habema 87520b0
patch up tests
habema 6cfc505
rename AdvancedSQLiteSession example files
habema 967a06a
code review fixes
habema 09becf0
fix old_version_tests
habema 9959e67
rename methods to improve clarity and consistency
habema 50e98bc
replace soft deletion approach with proper branching
habema a6ce7ce
remove more soft deletion traces
habema 372e88c
Merge branch 'main' into session-schema-improvements
habema 714bd25
Merge branch 'main' into session-schema-improvements
habema 1288548
code review
habema 2850674
merge the two example scripts into one
habema b242eee
better docstrings and organize code
habema 05e3e6d
Merge branch 'main' into session-schema-improvements
habema File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
""" | ||
Basic example demonstrating advanced session memory functionality. | ||
|
||
This example shows how to use AdvancedSQLiteSession for conversation tracking | ||
with usage statistics and turn-based organization. | ||
""" | ||
|
||
import asyncio | ||
|
||
from agents import Agent, Runner, function_tool | ||
from agents.extensions.memory import AdvancedSQLiteSession | ||
|
||
|
||
@function_tool | ||
async def get_weather(city: str) -> str: | ||
if city.strip().lower() == "new york": | ||
return f"The weather in {city} is cloudy." | ||
return f"The weather in {city} is sunny." | ||
|
||
|
||
async def main(): | ||
# Create an agent | ||
agent = Agent( | ||
name="Assistant", | ||
instructions="Reply very concisely.", | ||
tools=[get_weather], | ||
) | ||
|
||
# Create a advanced session instance | ||
session = AdvancedSQLiteSession(session_id="conversation_basic", create_tables=True) | ||
|
||
print("=== Basic Advanced Session Example ===") | ||
print("The agent will remember previous messages with structured tracking.\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(f"Usage: {result.context_wrapper.usage.total_tokens} tokens") | ||
|
||
# Store usage data automatically | ||
await session.store_run_usage(result) | ||
print() | ||
|
||
# Second turn - continuing the conversation | ||
print("Second turn:") | ||
print("User: What's the weather in that city?") | ||
result = await Runner.run( | ||
agent, | ||
"What's the weather in that city?", | ||
session=session, | ||
) | ||
print(f"Assistant: {result.final_output}") | ||
print(f"Usage: {result.context_wrapper.usage.total_tokens} tokens") | ||
|
||
# Store usage data automatically | ||
await session.store_run_usage(result) | ||
print() | ||
|
||
print("=== Usage Tracking Demo ===") | ||
session_usage = await session.get_session_usage() | ||
if session_usage: | ||
print("Session Usage (aggregated from turns):") | ||
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']}") | ||
|
||
# Show usage by turn | ||
turn_usage_list = await session.get_turn_usage() | ||
if turn_usage_list and isinstance(turn_usage_list, list): | ||
print("\nUsage by turn:") | ||
for turn_data in turn_usage_list: | ||
turn_num = turn_data["user_turn_number"] | ||
tokens = turn_data["total_tokens"] | ||
print(f" Turn {turn_num}: {tokens} tokens") | ||
else: | ||
print("No usage data found.") | ||
|
||
print("\n=== Structured Query Demo ===") | ||
conversation_turns = await session.get_conversation_by_turns() | ||
print("Conversation by turns:") | ||
for turn_num, items in conversation_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']}") | ||
|
||
# Show tool usage | ||
tool_usage = await session.get_tool_usage() | ||
if tool_usage: | ||
print("\nTool usage:") | ||
for tool_name, count, turn in tool_usage: | ||
print(f" {tool_name}: used {count} times in turn {turn}") | ||
else: | ||
print("\nNo tool usage found.") | ||
|
||
print("\n=== Basic Example Complete ===") | ||
print("Session provides structured tracking with usage analytics!") | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
""" | ||
Advanced example demonstrating conversation branching with AdvancedSQLiteSession. | ||
|
||
This example shows how to use the new conversation branching system, | ||
allowing you to branch conversations from any user message and manage multiple timelines. | ||
""" | ||
|
||
import asyncio | ||
|
||
from agents import Agent, Runner, function_tool | ||
from agents.extensions.memory import AdvancedSQLiteSession | ||
|
||
|
||
@function_tool | ||
async def get_weather(city: str) -> str: | ||
if city.strip().lower() == "new york": | ||
return f"The weather in {city} is cloudy." | ||
return f"The weather in {city} is sunny." | ||
|
||
|
||
async def main(): | ||
# Create an agent | ||
agent = Agent( | ||
name="Assistant", | ||
instructions="Reply very concisely.", | ||
tools=[get_weather], | ||
) | ||
|
||
# Create an advanced session instance | ||
session = AdvancedSQLiteSession( | ||
session_id="conversation_advanced", | ||
create_tables=True, | ||
) | ||
|
||
print("=== Advanced Session: Conversation Branching ===") | ||
print("This example demonstrates the new conversation branching system.\n") | ||
|
||
# Build initial conversation | ||
print("Building initial conversation...") | ||
|
||
# Turn 1 | ||
print("Turn 1: User asks about Golden Gate Bridge") | ||
result = await Runner.run( | ||
agent, | ||
"What city is the Golden Gate Bridge in?", | ||
session=session, | ||
) | ||
print(f"Assistant: {result.final_output}") | ||
await session.store_run_usage(result) | ||
|
||
# Turn 2 | ||
print("Turn 2: User asks about weather") | ||
result = await Runner.run( | ||
agent, | ||
"What's the weather in that city?", | ||
session=session, | ||
) | ||
print(f"Assistant: {result.final_output}") | ||
await session.store_run_usage(result) | ||
|
||
# Turn 3 | ||
print("Turn 3: User asks about population") | ||
result = await Runner.run( | ||
agent, | ||
"What's the population of that city?", | ||
session=session, | ||
) | ||
print(f"Assistant: {result.final_output}") | ||
await session.store_run_usage(result) | ||
|
||
print("\n=== Original Conversation Complete ===") | ||
|
||
# Show current conversation | ||
print("Current conversation:") | ||
current_items = await session.get_items() | ||
for i, item in enumerate(current_items, 1): | ||
role = str(item.get("role", item.get("type", "unknown"))) | ||
if item.get("type") == "function_call": | ||
content = f"{item.get('name', 'unknown')}({item.get('arguments', '{}')})" | ||
elif item.get("type") == "function_call_output": | ||
content = str(item.get("output", "")) | ||
else: | ||
content = str(item.get("content", item.get("output", ""))) | ||
print(f" {i}. {role}: {content}") | ||
|
||
print(f"\nTotal items: {len(current_items)}") | ||
|
||
# Demonstrate conversation branching | ||
print("\n=== Conversation Branching Demo ===") | ||
print("Let's explore a different path from turn 2...") | ||
|
||
# Show available turns for branching | ||
print("\nAvailable turns for branching:") | ||
turns = await session.get_conversation_turns() | ||
for turn in turns: | ||
print(f" Turn {turn['turn']}: {turn['content']}") | ||
|
||
# Create a branch from turn 2 | ||
print("\nCreating new branch from turn 2...") | ||
branch_id = await session.create_branch_from_turn(2) | ||
print(f"Created branch: {branch_id}") | ||
|
||
# Show what's in the new branch (should have conversation up to turn 2) | ||
branch_items = await session.get_items() | ||
print(f"Items copied to new branch: {len(branch_items)}") | ||
print("New branch contains:") | ||
for i, item in enumerate(branch_items, 1): | ||
role = str(item.get("role", item.get("type", "unknown"))) | ||
if item.get("type") == "function_call": | ||
content = f"{item.get('name', 'unknown')}({item.get('arguments', '{}')})" | ||
elif item.get("type") == "function_call_output": | ||
content = str(item.get("output", "")) | ||
else: | ||
content = str(item.get("content", item.get("output", ""))) | ||
print(f" {i}. {role}: {content}") | ||
|
||
# Continue conversation in new branch | ||
print("\nContinuing conversation in new branch...") | ||
print("Turn 2 (new branch): User asks about New York instead") | ||
result = await Runner.run( | ||
agent, | ||
"Actually, what's the weather in New York instead?", | ||
session=session, | ||
) | ||
print(f"Assistant: {result.final_output}") | ||
await session.store_run_usage(result) | ||
|
||
# Continue the new branch | ||
print("Turn 3 (new branch): User asks about NYC attractions") | ||
result = await Runner.run( | ||
agent, | ||
"What are some famous attractions in New York?", | ||
session=session, | ||
) | ||
print(f"Assistant: {result.final_output}") | ||
await session.store_run_usage(result) | ||
|
||
# Show the new conversation | ||
print("\n=== New Conversation Branch ===") | ||
new_conversation = await session.get_items() | ||
print("New conversation with branch:") | ||
for i, item in enumerate(new_conversation, 1): | ||
role = str(item.get("role", item.get("type", "unknown"))) | ||
if item.get("type") == "function_call": | ||
content = f"{item.get('name', 'unknown')}({item.get('arguments', '{}')})" | ||
elif item.get("type") == "function_call_output": | ||
content = str(item.get("output", "")) | ||
else: | ||
content = str(item.get("content", item.get("output", ""))) | ||
print(f" {i}. {role}: {content}") | ||
|
||
print(f"\nTotal items in new branch: {len(new_conversation)}") | ||
|
||
print("\n=== Branch Management ===") | ||
# Show all branches | ||
branches = await session.list_branches() | ||
print("All branches in this session:") | ||
for branch in branches: | ||
current = " (current)" if branch["is_current"] else "" | ||
print( | ||
f" {branch['branch_id']}: {branch['user_turns']} user turns, {branch['message_count']} total messages{current}" | ||
) | ||
|
||
# Show conversation turns in current branch | ||
print("\nConversation turns in current branch:") | ||
current_turns = await session.get_conversation_turns() | ||
for turn in current_turns: | ||
print(f" Turn {turn['turn']}: {turn['content']}") | ||
|
||
print("\n=== Branch Switching Demo ===") | ||
print("We can switch back to the main branch...") | ||
|
||
# Switch back to main branch | ||
await session.switch_to_branch("main") | ||
print("Switched to main branch") | ||
|
||
# Show what's in main branch | ||
main_items = await session.get_items() | ||
print(f"Items in main branch: {len(main_items)}") | ||
|
||
# Switch back to new branch | ||
await session.switch_to_branch(branch_id) | ||
branch_items = await session.get_items() | ||
print(f"Items in new branch: {len(branch_items)}") | ||
|
||
print("\n=== Final Summary ===") | ||
await session.switch_to_branch("main") | ||
main_final = len(await session.get_items()) | ||
await session.switch_to_branch(branch_id) | ||
branch_final = len(await session.get_items()) | ||
|
||
print(f"Main branch items: {main_final}") | ||
print(f"New branch items: {branch_final}") | ||
|
||
# Show that branches are completely independent | ||
print("\nBranches are completely independent:") | ||
print("- Main branch has full original conversation") | ||
print("- New branch has turn 1 + new conversation path") | ||
print("- No interference between branches!") | ||
|
||
print("\n=== Advanced Example Complete ===") | ||
print("This demonstrates the new conversation branching system!") | ||
print("Key features:") | ||
print("- Create branches from any user message") | ||
print("- Branches inherit conversation history up to the branch point") | ||
print("- Complete branch isolation - no interference between branches") | ||
print("- Easy branch switching and management") | ||
print("- No complex soft deletion - clean branch-based architecture") | ||
print("- Perfect for building AI systems with conversation editing capabilities!") | ||
|
||
# Cleanup | ||
session.close() | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.