From ee1661ecb7d38284098eba97f66ad8befa8b9445 Mon Sep 17 00:00:00 2001 From: Giles Odigwe <79032838+giles17@users.noreply.github.com> Date: Wed, 5 Nov 2025 18:59:22 -0800 Subject: [PATCH] Python: Thread Samples Fix (#1945) * thread samples fix * custom chat message store fix --- .../samples/getting_started/threads/README.md | 11 +++- .../custom_chat_message_store_thread.py | 29 +++++++-- .../redis_chat_message_store_thread.py | 8 +++ .../threads/suspend_resume_thread.py | 61 ++++++++++++------- 4 files changed, 79 insertions(+), 30 deletions(-) diff --git a/python/samples/getting_started/threads/README.md b/python/samples/getting_started/threads/README.md index d1ece041f7..32c19d537f 100644 --- a/python/samples/getting_started/threads/README.md +++ b/python/samples/getting_started/threads/README.md @@ -7,5 +7,14 @@ This folder contains examples demonstrating different ways to manage conversatio | 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. | -| [`suspend_resume_thread.py`](suspend_resume_thread.py) | Shows how to suspend and resume conversation threads, allowing you to save the state of a conversation and continue it later. This is useful for long-running conversations or when you need to persist conversation state across application restarts. | | [`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) diff --git a/python/samples/getting_started/threads/custom_chat_message_store_thread.py b/python/samples/getting_started/threads/custom_chat_message_store_thread.py index f4a9d79112..266bb095f2 100644 --- a/python/samples/getting_started/threads/custom_chat_message_store_thread.py +++ b/python/samples/getting_started/threads/custom_chat_message_store_thread.py @@ -8,6 +8,14 @@ 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. @@ -24,13 +32,22 @@ class CustomChatMessageStore(ChatMessageStoreProtocol): async def list_messages(self) -> list[ChatMessage]: return self._messages - async def deserialize_state(self, serialized_store_state: Any, **kwargs: Any) -> None: + @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_state(self, **kwargs: Any) -> Any: + async def serialize(self, **kwargs: Any) -> Any: + """Serialize this store's state.""" state = ChatMessageStoreState(messages=self._messages) return state.to_dict(**kwargs) @@ -42,8 +59,8 @@ async def main() -> None: # OpenAI Chat Client is used as an example here, # other chat clients can be used as well. agent = OpenAIChatClient().create_agent( - name="Joker", - instructions="You are good at telling jokes.", + 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, @@ -53,7 +70,7 @@ async def main() -> None: thread = agent.get_new_thread() # Respond to user input. - query = "Tell me a joke about a pirate." + query = "Hello! My name is Alice and I love pizza." print(f"User: {query}") print(f"Agent: {await agent.run(query, thread=thread)}\n") @@ -67,7 +84,7 @@ async def main() -> None: resumed_thread = await agent.deserialize_thread(serialized_thread) # Respond to user input. - query = "Now tell the same joke in the voice of a pirate, and add some emojis to the joke." + query = "What do you remember about me?" print(f"User: {query}") print(f"Agent: {await agent.run(query, thread=resumed_thread)}\n") diff --git a/python/samples/getting_started/threads/redis_chat_message_store_thread.py b/python/samples/getting_started/threads/redis_chat_message_store_thread.py index 30d647fd40..d4b8f03c26 100644 --- a/python/samples/getting_started/threads/redis_chat_message_store_thread.py +++ b/python/samples/getting_started/threads/redis_chat_message_store_thread.py @@ -8,6 +8,14 @@ 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.""" diff --git a/python/samples/getting_started/threads/suspend_resume_thread.py b/python/samples/getting_started/threads/suspend_resume_thread.py index 88065aa987..99880fe8ab 100644 --- a/python/samples/getting_started/threads/suspend_resume_thread.py +++ b/python/samples/getting_started/threads/suspend_resume_thread.py @@ -2,38 +2,51 @@ 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 ===") - # OpenAI Chat Client is used as an example here, - # other chat clients can be used as well. - agent = OpenAIChatClient().create_agent(name="Joker", instructions="You are good at telling jokes.") + # AzureAIAgentClient supports service-managed threads. + async with ( + AzureCliCredential() as credential, + AzureAIAgentClient(async_credential=credential).create_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() - # 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") - # Respond to user input. - query = "Tell me a joke about a pirate." - 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() - # 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") - # 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) - # Deserialize the thread state after loading from storage. - resumed_thread = await agent.deserialize_thread(serialized_thread) - - # Respond to user input. - query = "Now tell the same joke in the voice of a pirate, and add some emojis to the joke." - print(f"User: {query}") - print(f"Agent: {await agent.run(query, thread=resumed_thread)}\n") + # 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: @@ -42,13 +55,15 @@ async def suspend_resume_in_memory_thread() -> None: # OpenAI Chat Client is used as an example here, # other chat clients can be used as well. - agent = OpenAIChatClient().create_agent(name="Joker", instructions="You are good at telling jokes.") + agent = OpenAIChatClient().create_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 = "Tell me a joke about a pirate." + query = "Hello! My name is Alice and I love pizza." print(f"User: {query}") print(f"Agent: {await agent.run(query, thread=thread)}\n") @@ -62,7 +77,7 @@ async def suspend_resume_in_memory_thread() -> None: resumed_thread = await agent.deserialize_thread(serialized_thread) # Respond to user input. - query = "Now tell the same joke in the voice of a pirate, and add some emojis to the joke." + query = "What do you remember about me?" print(f"User: {query}") print(f"Agent: {await agent.run(query, thread=resumed_thread)}\n")