test
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
Python - Merge - Tests / paths-filter (push) Has been cancelled
Python - Merge - Tests / Python Tests - Core (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / Python Tests - Azure AI (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / python-integration-tests-check (push) Has been cancelled
Python - Lab Tests / paths-filter (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.14) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.14) (push) Has been cancelled
Check .md links / markdown-link-check (push) Has been cancelled

This commit is contained in:
2026-01-24 03:05:12 +11:00
parent f78f2388b3
commit 539852f81c
2584 changed files with 287471 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
# Thread Management Examples
This folder contains examples demonstrating different ways to manage conversation threads and chat message stores with the Agent Framework.
## Examples
| File | Description |
|------|-------------|
| [`custom_chat_message_store_thread.py`](custom_chat_message_store_thread.py) | Demonstrates how to implement a custom `ChatMessageStore` for persisting conversation history. Shows how to create a custom store with serialization/deserialization capabilities and integrate it with agents for thread management across multiple sessions. |
| [`redis_chat_message_store_thread.py`](redis_chat_message_store_thread.py) | Comprehensive examples of using the Redis-backed `RedisChatMessageStore` for persistent conversation storage. Covers basic usage, user session management, conversation persistence across app restarts, thread serialization, and automatic message trimming. Requires Redis server and demonstrates production-ready patterns for scalable chat applications. |
| [`suspend_resume_thread.py`](suspend_resume_thread.py) | Shows how to suspend and resume conversation threads, comparing service-managed threads (Azure AI) with in-memory threads (OpenAI). Demonstrates saving conversation state and continuing it later, useful for long-running conversations or persisting state across application restarts. |
## Environment Variables
Make sure to set the following environment variables before running the examples:
- `OPENAI_API_KEY`: Your OpenAI API key (required for all samples)
- `OPENAI_CHAT_MODEL_ID`: The OpenAI model to use (e.g., `gpt-4o`, `gpt-4o-mini`, `gpt-3.5-turbo`) (required for all samples)
- `AZURE_AI_PROJECT_ENDPOINT`: Azure AI Project endpoint URL (required for service-managed thread examples)
- `AZURE_AI_MODEL_DEPLOYMENT_NAME`: The name of your model deployment (required for service-managed thread examples)

View File

@@ -0,0 +1,93 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from collections.abc import Collection
from typing import Any
from agent_framework import ChatMessage, ChatMessageStoreProtocol
from agent_framework._threads import ChatMessageStoreState
from agent_framework.openai import OpenAIChatClient
"""
Custom Chat Message Store Thread Example
This sample demonstrates how to implement and use a custom chat message store
for thread management, allowing you to persist conversation history in your
preferred storage solution (database, file system, etc.).
"""
class CustomChatMessageStore(ChatMessageStoreProtocol):
"""Implementation of custom chat message store.
In real applications, this can be an implementation of relational database or vector store."""
def __init__(self, messages: Collection[ChatMessage] | None = None) -> None:
self._messages: list[ChatMessage] = []
if messages:
self._messages.extend(messages)
async def add_messages(self, messages: Collection[ChatMessage]) -> None:
self._messages.extend(messages)
async def list_messages(self) -> list[ChatMessage]:
return self._messages
@classmethod
async def deserialize(cls, serialized_store_state: Any, **kwargs: Any) -> "CustomChatMessageStore":
"""Create a new instance from serialized state."""
store = cls()
await store.update_from_state(serialized_store_state, **kwargs)
return store
async def update_from_state(self, serialized_store_state: Any, **kwargs: Any) -> None:
"""Update this instance from serialized state."""
if serialized_store_state:
state = ChatMessageStoreState.from_dict(serialized_store_state, **kwargs)
if state.messages:
self._messages.extend(state.messages)
async def serialize(self, **kwargs: Any) -> Any:
"""Serialize this store's state."""
state = ChatMessageStoreState(messages=self._messages)
return state.to_dict(**kwargs)
async def main() -> None:
"""Demonstrates how to use 3rd party or custom chat message store for threads."""
print("=== Thread with 3rd party or custom chat message store ===")
# OpenAI Chat Client is used as an example here,
# other chat clients can be used as well.
agent = OpenAIChatClient().as_agent(
name="CustomBot",
instructions="You are a helpful assistant that remembers our conversation.",
# Use custom chat message store.
# If not provided, the default in-memory store will be used.
chat_message_store_factory=CustomChatMessageStore,
)
# Start a new thread for the agent conversation.
thread = agent.get_new_thread()
# Respond to user input.
query = "Hello! My name is Alice and I love pizza."
print(f"User: {query}")
print(f"Agent: {await agent.run(query, thread=thread)}\n")
# Serialize the thread state, so it can be stored for later use.
serialized_thread = await thread.serialize()
# The thread can now be saved to a database, file, or any other storage mechanism and loaded again later.
print(f"Serialized thread: {serialized_thread}\n")
# Deserialize the thread state after loading from storage.
resumed_thread = await agent.deserialize_thread(serialized_thread)
# Respond to user input.
query = "What do you remember about me?"
print(f"User: {query}")
print(f"Agent: {await agent.run(query, thread=resumed_thread)}\n")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,322 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from uuid import uuid4
from agent_framework import AgentThread
from agent_framework.openai import OpenAIChatClient
from agent_framework.redis import RedisChatMessageStore
"""
Redis Chat Message Store Thread Example
This sample demonstrates how to use Redis as a chat message store for thread
management, enabling persistent conversation history storage across sessions
with Redis as the backend data store.
"""
async def example_manual_memory_store() -> None:
"""Basic example of using Redis chat message store."""
print("=== Basic Redis Chat Message Store Example ===")
# Create Redis store with auto-generated thread ID
redis_store = RedisChatMessageStore(
redis_url="redis://localhost:6379",
# thread_id will be auto-generated if not provided
)
print(f"Created store with thread ID: {redis_store.thread_id}")
# Create thread with Redis store
thread = AgentThread(message_store=redis_store)
# Create agent
agent = OpenAIChatClient().as_agent(
name="RedisBot",
instructions="You are a helpful assistant that remembers our conversation using Redis.",
)
# Have a conversation
print("\n--- Starting conversation ---")
query1 = "Hello! My name is Alice and I love pizza."
print(f"User: {query1}")
response1 = await agent.run(query1, thread=thread)
print(f"Agent: {response1.text}")
query2 = "What do you remember about me?"
print(f"User: {query2}")
response2 = await agent.run(query2, thread=thread)
print(f"Agent: {response2.text}")
# Show messages are stored in Redis
messages = await redis_store.list_messages()
print(f"\nTotal messages in Redis: {len(messages)}")
# Cleanup
await redis_store.clear()
await redis_store.aclose()
print("Cleaned up Redis data\n")
async def example_user_session_management() -> None:
"""Example of managing user sessions with Redis."""
print("=== User Session Management Example ===")
user_id = "alice_123"
session_id = f"session_{uuid4()}"
# Create Redis store for specific user session
def create_user_session_store():
return RedisChatMessageStore(
redis_url="redis://localhost:6379",
thread_id=f"user_{user_id}_{session_id}",
max_messages=10, # Keep only last 10 messages
)
# Create agent with factory pattern
agent = OpenAIChatClient().as_agent(
name="SessionBot",
instructions="You are a helpful assistant. Keep track of user preferences.",
chat_message_store_factory=create_user_session_store,
)
# Start conversation
thread = agent.get_new_thread()
print(f"Started session for user {user_id}")
if hasattr(thread.message_store, "thread_id"):
print(f"Thread ID: {thread.message_store.thread_id}") # type: ignore[union-attr]
# Simulate conversation
queries = [
"Hi, I'm Alice and I prefer vegetarian food.",
"What restaurants would you recommend?",
"I also love Italian cuisine.",
"Can you remember my food preferences?",
]
for i, query in enumerate(queries, 1):
print(f"\n--- Message {i} ---")
print(f"User: {query}")
response = await agent.run(query, thread=thread)
print(f"Agent: {response.text}")
# Show persistent storage
if thread.message_store:
messages = await thread.message_store.list_messages() # type: ignore[union-attr]
print(f"\nMessages stored for user {user_id}: {len(messages)}")
# Cleanup
if thread.message_store:
await thread.message_store.clear() # type: ignore[union-attr]
await thread.message_store.aclose() # type: ignore[union-attr]
print("Cleaned up session data\n")
async def example_conversation_persistence() -> None:
"""Example of conversation persistence across application restarts."""
print("=== Conversation Persistence Example ===")
conversation_id = "persistent_chat_001"
# Phase 1: Start conversation
print("--- Phase 1: Starting conversation ---")
store1 = RedisChatMessageStore(
redis_url="redis://localhost:6379",
thread_id=conversation_id,
)
thread1 = AgentThread(message_store=store1)
agent = OpenAIChatClient().as_agent(
name="PersistentBot",
instructions="You are a helpful assistant. Remember our conversation history.",
)
# Start conversation
query1 = "Hello! I'm working on a Python project about machine learning."
print(f"User: {query1}")
response1 = await agent.run(query1, thread=thread1)
print(f"Agent: {response1.text}")
query2 = "I'm specifically interested in neural networks."
print(f"User: {query2}")
response2 = await agent.run(query2, thread=thread1)
print(f"Agent: {response2.text}")
print(f"Stored {len(await store1.list_messages())} messages in Redis")
await store1.aclose()
# Phase 2: Resume conversation (simulating app restart)
print("\n--- Phase 2: Resuming conversation (after 'restart') ---")
store2 = RedisChatMessageStore(
redis_url="redis://localhost:6379",
thread_id=conversation_id, # Same thread ID
)
thread2 = AgentThread(message_store=store2)
# Continue conversation - agent should remember context
query3 = "What was I working on before?"
print(f"User: {query3}")
response3 = await agent.run(query3, thread=thread2)
print(f"Agent: {response3.text}")
query4 = "Can you suggest some Python libraries for neural networks?"
print(f"User: {query4}")
response4 = await agent.run(query4, thread=thread2)
print(f"Agent: {response4.text}")
print(f"Total messages after resuming: {len(await store2.list_messages())}")
# Cleanup
await store2.clear()
await store2.aclose()
print("Cleaned up persistent data\n")
async def example_thread_serialization() -> None:
"""Example of thread state serialization and deserialization."""
print("=== Thread Serialization Example ===")
# Create initial thread with Redis store
original_store = RedisChatMessageStore(
redis_url="redis://localhost:6379",
thread_id="serialization_test",
max_messages=50,
)
original_thread = AgentThread(message_store=original_store)
agent = OpenAIChatClient().as_agent(
name="SerializationBot",
instructions="You are a helpful assistant.",
)
# Have initial conversation
print("--- Initial conversation ---")
query1 = "Hello! I'm testing serialization."
print(f"User: {query1}")
response1 = await agent.run(query1, thread=original_thread)
print(f"Agent: {response1.text}")
# Serialize thread state
serialized_thread = await original_thread.serialize()
print(f"\nSerialized thread state: {serialized_thread}")
# Close original connection
await original_store.aclose()
# Deserialize thread state (simulating loading from database/file)
print("\n--- Deserializing thread state ---")
# Create a new thread with the same Redis store type
# This ensures the correct store type is used for deserialization
restored_store = RedisChatMessageStore(redis_url="redis://localhost:6379")
restored_thread = await AgentThread.deserialize(serialized_thread, message_store=restored_store)
# Continue conversation with restored thread
query2 = "Do you remember what I said about testing?"
print(f"User: {query2}")
response2 = await agent.run(query2, thread=restored_thread)
print(f"Agent: {response2.text}")
# Cleanup
if restored_thread.message_store:
await restored_thread.message_store.clear() # type: ignore[union-attr]
await restored_thread.message_store.aclose() # type: ignore[union-attr]
print("Cleaned up serialization test data\n")
async def example_message_limits() -> None:
"""Example of automatic message trimming with limits."""
print("=== Message Limits Example ===")
# Create store with small message limit
store = RedisChatMessageStore(
redis_url="redis://localhost:6379",
thread_id="limits_test",
max_messages=3, # Keep only 3 most recent messages
)
thread = AgentThread(message_store=store)
agent = OpenAIChatClient().as_agent(
name="LimitBot",
instructions="You are a helpful assistant with limited memory.",
)
# Send multiple messages to test trimming
messages = [
"Message 1: Hello!",
"Message 2: How are you?",
"Message 3: What's the weather?",
"Message 4: Tell me a joke.",
"Message 5: This should trigger trimming.",
]
for i, query in enumerate(messages, 1):
print(f"\n--- Sending message {i} ---")
print(f"User: {query}")
response = await agent.run(query, thread=thread)
print(f"Agent: {response.text}")
stored_messages = await store.list_messages()
print(f"Messages in store: {len(stored_messages)}")
if len(stored_messages) > 0:
print(f"Oldest message: {stored_messages[0].text[:30]}...")
# Final check
final_messages = await store.list_messages()
print(f"\nFinal message count: {len(final_messages)} (should be <= 6: 3 messages × 2 per exchange)")
# Cleanup
await store.clear()
await store.aclose()
print("Cleaned up limits test data\n")
async def main() -> None:
"""Run all Redis chat message store examples."""
print("Redis Chat Message Store Examples")
print("=" * 50)
print("Prerequisites:")
print("- Redis server running on localhost:6379")
print("- OPENAI_API_KEY environment variable set")
print("=" * 50)
# Check prerequisites
if not os.getenv("OPENAI_API_KEY"):
print("ERROR: OPENAI_API_KEY environment variable not set")
return
try:
# Test Redis connection
test_store = RedisChatMessageStore(redis_url="redis://localhost:6379")
connection_ok = await test_store.ping()
await test_store.aclose()
if not connection_ok:
raise Exception("Redis ping failed")
print("✓ Redis connection successful\n")
except Exception as e:
print(f"ERROR: Cannot connect to Redis: {e}")
print("Please ensure Redis is running on localhost:6379")
return
try:
# Run all examples
await example_manual_memory_store()
await example_user_session_management()
await example_conversation_persistence()
await example_thread_serialization()
await example_message_limits()
print("All examples completed successfully!")
except Exception as e:
print(f"Error running examples: {e}")
raise
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,92 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework.azure import AzureAIAgentClient
from agent_framework.openai import OpenAIChatClient
from azure.identity.aio import AzureCliCredential
"""
Thread Suspend and Resume Example
This sample demonstrates how to suspend and resume conversation threads, comparing
service-managed threads (Azure AI) with in-memory threads (OpenAI) for persistent
conversation state across sessions.
"""
async def suspend_resume_service_managed_thread() -> None:
"""Demonstrates how to suspend and resume a service-managed thread."""
print("=== Suspend-Resume Service-Managed Thread ===")
# AzureAIAgentClient supports service-managed threads.
async with (
AzureCliCredential() as credential,
AzureAIAgentClient(credential=credential).as_agent(
name="MemoryBot", instructions="You are a helpful assistant that remembers our conversation."
) as agent,
):
# Start a new thread for the agent conversation.
thread = agent.get_new_thread()
# Respond to user input.
query = "Hello! My name is Alice and I love pizza."
print(f"User: {query}")
print(f"Agent: {await agent.run(query, thread=thread)}\n")
# Serialize the thread state, so it can be stored for later use.
serialized_thread = await thread.serialize()
# The thread can now be saved to a database, file, or any other storage mechanism and loaded again later.
print(f"Serialized thread: {serialized_thread}\n")
# Deserialize the thread state after loading from storage.
resumed_thread = await agent.deserialize_thread(serialized_thread)
# Respond to user input.
query = "What do you remember about me?"
print(f"User: {query}")
print(f"Agent: {await agent.run(query, thread=resumed_thread)}\n")
async def suspend_resume_in_memory_thread() -> None:
"""Demonstrates how to suspend and resume an in-memory thread."""
print("=== Suspend-Resume In-Memory Thread ===")
# OpenAI Chat Client is used as an example here,
# other chat clients can be used as well.
agent = OpenAIChatClient().as_agent(
name="MemoryBot", instructions="You are a helpful assistant that remembers our conversation."
)
# Start a new thread for the agent conversation.
thread = agent.get_new_thread()
# Respond to user input.
query = "Hello! My name is Alice and I love pizza."
print(f"User: {query}")
print(f"Agent: {await agent.run(query, thread=thread)}\n")
# Serialize the thread state, so it can be stored for later use.
serialized_thread = await thread.serialize()
# The thread can now be saved to a database, file, or any other storage mechanism and loaded again later.
print(f"Serialized thread: {serialized_thread}\n")
# Deserialize the thread state after loading from storage.
resumed_thread = await agent.deserialize_thread(serialized_thread)
# Respond to user input.
query = "What do you remember about me?"
print(f"User: {query}")
print(f"Agent: {await agent.run(query, thread=resumed_thread)}\n")
async def main() -> None:
print("=== Suspend-Resume Thread Examples ===")
await suspend_resume_service_managed_thread()
await suspend_resume_in_memory_thread()
if __name__ == "__main__":
asyncio.run(main())