mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [BREAKING] PR2 — Wire context provider pipeline, remove old types, update all consumers (#3850)
* PR2: Wire context provider pipeline and update all internal consumers - Replace AgentThread with AgentSession across all packages - Replace ContextProvider with BaseContextProvider across all packages - Replace context_provider param with context_providers (Sequence) - Replace thread= with session= in run() signatures - Replace get_new_thread() with create_session() - Add get_session(service_session_id) to agent interface - DurableAgentThread -> DurableAgentSession - Remove _notify_thread_of_new_messages from WorkflowAgent - Wire before_run/after_run context provider pipeline in RawAgent - Auto-inject InMemoryHistoryProvider when no providers configured * fix: update all tests for context provider pipeline, fix lazy-loaders, remove old test files * refactor: update all sample files for context provider pipeline (AgentThread→AgentSession, ContextProvider→BaseContextProvider) * fix: update remaining ag-ui references (client docstring, getting_started sample) * fix: make get_session service_session_id keyword-only to avoid confusion with session_id * refactor: rename _RunContext.thread_messages to session_messages * refactor: remove _threads.py, _memory.py, and old provider files; migrate devui to use plain message lists * rename: remove _new_ prefix from test files * refactor: rewrite SlidingWindowChatMessageStore as SlidingWindowHistoryProvider(InMemoryHistoryProvider) * fix: read full history from session state directly instead of reaching into provider internals * fix: update stale .pyi stubs, sample imports, and README references for new provider types * fix: remove stale message_store, _notify_thread_of_new_messages, and session_id.key references in samples * refactor: merge context_providers and sessions sample folders into sessions, remove aggregate_context_provider * refactor: UserInfoMemory stores state in session.state instead of instance attributes * feat: add Pydantic BaseModel support to session state serialization Pydantic models stored in session.state are now automatically serialized via model_dump() and restored via model_validate() during to_dict()/from_dict() round-trips. Models are auto-registered on first serialization; use register_state_type() for cold-start deserialization. Also export register_state_type as a public API. * fix mem0 * Update sample README links and descriptions for session terminology - Replace 'thread' with 'session' in sample descriptions across all READMEs - Update file links for renamed samples (mem0_sessions, redis_sessions, etc.) - Fix Threads section → Sessions section in main samples/README.md - Update tools, middleware, workflows, durabletask, azure_functions READMEs - Update architecture diagrams in concepts/tools/README.md - Update migration guides (autogen, semantic-kernel) * Fix broken Redis README link to renamed sample * Fix Mem0 OSS client search: pass scoping params as direct kwargs AsyncMemory (OSS) expects user_id/agent_id/run_id as direct kwargs, while AsyncMemoryClient (Platform) expects them in a filters dict. Adds tests for both client types. Port of fix from #3844 to new Mem0ContextProvider. * Fix rebase issues: restore missing _conversation_state.py and checkpoint decode logic - Add back _conversation_state.py (encode/decode_chat_messages) lost in rebase - Fix on_checkpoint_restore to decode cache/conversation with decode_chat_messages - Fix on_checkpoint_restore to use decode_checkpoint_value for pending requests - Add tests/workflow/__init__.py for relative import support - Fix test_agent_executor checkpoint selection (checkpoints[1] not superstep) * Add STORES_BY_DEFAULT ClassVar to skip redundant InMemoryHistoryProvider injection Chat clients that store history server-side by default (OpenAI Responses API, Azure AI Agent) now declare STORES_BY_DEFAULT = True. The agent checks this during auto-injection and skips InMemoryHistoryProvider unless the user explicitly sets store=False. * Fix broken markdown links in azure_ai and redis READMEs * Fix getting-started samples to use session API instead of removed thread/ContextProvider API * updates to workflow as agent * fix group chat import * Rename Thread→Session throughout, fix service_session_id propagation, remove stale AGUIThread - Fix: Propagate conversation_id from ChatResponse back to session.service_session_id in both streaming and non-streaming paths in _agents.py - Rename AgentThreadException → AgentSessionException - Remove stale AGUIThread from ag_ui lazy-loader - Rename use_service_thread → use_service_session in ag-ui package - Rename test functions from *_thread_* to *_session_* - Rename sample files from *_thread* to *_session* - Update docstrings and comments: thread → session - Update _mcp.py kwargs filter: add 'session' alongside 'thread' - Fix ContinuationToken docstring example: thread=thread → session=session - Fix _clients.py docstring: 'Agent threads' → 'Agent sessions' * Fix broken markdown links after thread→session file renames * fix azure ai test
This commit is contained in:
committed by
GitHub
Unverified
parent
0c67dbbce5
commit
1e350ea22f
@@ -0,0 +1,85 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import AgentSession, BaseHistoryProvider, Message
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
"""
|
||||
Custom History Provider Example
|
||||
|
||||
This sample demonstrates how to implement and use a custom history provider
|
||||
for session management, allowing you to persist conversation history in your
|
||||
preferred storage solution (database, file system, etc.).
|
||||
"""
|
||||
|
||||
|
||||
class CustomHistoryProvider(BaseHistoryProvider):
|
||||
"""Implementation of custom history provider.
|
||||
In real applications, this can be an implementation of relational database or vector store."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("custom-history")
|
||||
self._storage: dict[str, list[Message]] = {}
|
||||
|
||||
async def get_messages(
|
||||
self, session_id: str | None, *, state: dict[str, Any] | None = None, **kwargs: Any
|
||||
) -> list[Message]:
|
||||
key = session_id or "default"
|
||||
return list(self._storage.get(key, []))
|
||||
|
||||
async def save_messages(
|
||||
self,
|
||||
session_id: str | None,
|
||||
messages: Sequence[Message],
|
||||
*,
|
||||
state: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
key = session_id or "default"
|
||||
if key not in self._storage:
|
||||
self._storage[key] = []
|
||||
self._storage[key].extend(messages)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Demonstrates how to use 3rd party or custom history provider for sessions."""
|
||||
print("=== Session with 3rd party or custom history provider ===")
|
||||
|
||||
# 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 history provider.
|
||||
# If not provided, the default in-memory provider will be used.
|
||||
context_providers=[CustomHistoryProvider()],
|
||||
)
|
||||
|
||||
# Start a new session for the agent conversation.
|
||||
session = agent.create_session()
|
||||
|
||||
# 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, session=session)}\n")
|
||||
|
||||
# Serialize the session state, so it can be stored for later use.
|
||||
serialized_session = session.to_dict()
|
||||
|
||||
# The session can now be saved to a database, file, or any other storage mechanism and loaded again later.
|
||||
print(f"Serialized session: {serialized_session}\n")
|
||||
|
||||
# Deserialize the session state after loading from storage.
|
||||
resumed_session = AgentSession.from_dict(serialized_session)
|
||||
|
||||
# Respond to user input.
|
||||
query = "What do you remember about me?"
|
||||
print(f"User: {query}")
|
||||
print(f"Agent: {await agent.run(query, session=resumed_session)}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,93 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Collection
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatMessageStoreProtocol, Message
|
||||
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[Message] | None = None) -> None:
|
||||
self._messages: list[Message] = []
|
||||
if messages:
|
||||
self._messages.extend(messages)
|
||||
|
||||
async def add_messages(self, messages: Collection[Message]) -> None:
|
||||
self._messages.extend(messages)
|
||||
|
||||
async def list_messages(self) -> list[Message]:
|
||||
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())
|
||||
@@ -0,0 +1,257 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
from agent_framework import AgentSession
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.redis import RedisHistoryProvider
|
||||
|
||||
"""
|
||||
Redis History Provider Session Example
|
||||
|
||||
This sample demonstrates how to use Redis as a history provider for session
|
||||
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 history provider."""
|
||||
print("=== Basic Redis History Provider Example ===")
|
||||
|
||||
# Create Redis history provider
|
||||
redis_provider = RedisHistoryProvider(
|
||||
source_id="redis_basic_chat",
|
||||
redis_url="redis://localhost:6379",
|
||||
)
|
||||
|
||||
# Create agent with Redis history provider
|
||||
agent = OpenAIChatClient().as_agent(
|
||||
name="RedisBot",
|
||||
instructions="You are a helpful assistant that remembers our conversation using Redis.",
|
||||
context_providers=[redis_provider],
|
||||
)
|
||||
|
||||
# Create session
|
||||
session = agent.create_session()
|
||||
|
||||
# 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, session=session)
|
||||
print(f"Agent: {response1.text}")
|
||||
|
||||
query2 = "What do you remember about me?"
|
||||
print(f"User: {query2}")
|
||||
response2 = await agent.run(query2, session=session)
|
||||
print(f"Agent: {response2.text}")
|
||||
|
||||
print("Done\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 history provider for specific user session
|
||||
redis_provider = RedisHistoryProvider(
|
||||
source_id=f"redis_{user_id}",
|
||||
redis_url="redis://localhost:6379",
|
||||
max_messages=10, # Keep only last 10 messages
|
||||
)
|
||||
|
||||
# Create agent with history provider
|
||||
agent = OpenAIChatClient().as_agent(
|
||||
name="SessionBot",
|
||||
instructions="You are a helpful assistant. Keep track of user preferences.",
|
||||
context_providers=[redis_provider],
|
||||
)
|
||||
|
||||
# Start conversation
|
||||
session = agent.create_session(session_id=session_id)
|
||||
|
||||
print(f"Started session for user {user_id}")
|
||||
|
||||
# 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, session=session)
|
||||
print(f"Agent: {response.text}")
|
||||
|
||||
print("Done\n")
|
||||
|
||||
|
||||
async def example_conversation_persistence() -> None:
|
||||
"""Example of conversation persistence across application restarts."""
|
||||
print("=== Conversation Persistence Example ===")
|
||||
|
||||
# Phase 1: Start conversation
|
||||
print("--- Phase 1: Starting conversation ---")
|
||||
redis_provider = RedisHistoryProvider(
|
||||
source_id="redis_persistent_chat",
|
||||
redis_url="redis://localhost:6379",
|
||||
)
|
||||
|
||||
agent = OpenAIChatClient().as_agent(
|
||||
name="PersistentBot",
|
||||
instructions="You are a helpful assistant. Remember our conversation history.",
|
||||
context_providers=[redis_provider],
|
||||
)
|
||||
|
||||
session = agent.create_session()
|
||||
|
||||
# Start conversation
|
||||
query1 = "Hello! I'm working on a Python project about machine learning."
|
||||
print(f"User: {query1}")
|
||||
response1 = await agent.run(query1, session=session)
|
||||
print(f"Agent: {response1.text}")
|
||||
|
||||
query2 = "I'm specifically interested in neural networks."
|
||||
print(f"User: {query2}")
|
||||
response2 = await agent.run(query2, session=session)
|
||||
print(f"Agent: {response2.text}")
|
||||
|
||||
# Serialize session state
|
||||
serialized = session.to_dict()
|
||||
|
||||
# Phase 2: Resume conversation (simulating app restart)
|
||||
print("\n--- Phase 2: Resuming conversation (after 'restart') ---")
|
||||
restored_session = AgentSession.from_dict(serialized)
|
||||
|
||||
# Continue conversation - agent should remember context
|
||||
query3 = "What was I working on before?"
|
||||
print(f"User: {query3}")
|
||||
response3 = await agent.run(query3, session=restored_session)
|
||||
print(f"Agent: {response3.text}")
|
||||
|
||||
query4 = "Can you suggest some Python libraries for neural networks?"
|
||||
print(f"User: {query4}")
|
||||
response4 = await agent.run(query4, session=restored_session)
|
||||
print(f"Agent: {response4.text}")
|
||||
|
||||
print("Done\n")
|
||||
|
||||
|
||||
async def example_session_serialization() -> None:
|
||||
"""Example of session state serialization and deserialization."""
|
||||
print("=== Session Serialization Example ===")
|
||||
|
||||
redis_provider = RedisHistoryProvider(
|
||||
source_id="redis_serialization_chat",
|
||||
redis_url="redis://localhost:6379",
|
||||
)
|
||||
|
||||
agent = OpenAIChatClient().as_agent(
|
||||
name="SerializationBot",
|
||||
instructions="You are a helpful assistant.",
|
||||
context_providers=[redis_provider],
|
||||
)
|
||||
|
||||
session = agent.create_session()
|
||||
|
||||
# Have initial conversation
|
||||
print("--- Initial conversation ---")
|
||||
query1 = "Hello! I'm testing serialization."
|
||||
print(f"User: {query1}")
|
||||
response1 = await agent.run(query1, session=session)
|
||||
print(f"Agent: {response1.text}")
|
||||
|
||||
# Serialize session state
|
||||
serialized = session.to_dict()
|
||||
print(f"\nSerialized session state: {serialized}")
|
||||
|
||||
# Deserialize session state (simulating loading from database/file)
|
||||
print("\n--- Deserializing session state ---")
|
||||
restored_session = AgentSession.from_dict(serialized)
|
||||
|
||||
# Continue conversation with restored session
|
||||
query2 = "Do you remember what I said about testing?"
|
||||
print(f"User: {query2}")
|
||||
response2 = await agent.run(query2, session=restored_session)
|
||||
print(f"Agent: {response2.text}")
|
||||
|
||||
print("Done\n")
|
||||
|
||||
|
||||
async def example_message_limits() -> None:
|
||||
"""Example of automatic message trimming with limits."""
|
||||
print("=== Message Limits Example ===")
|
||||
|
||||
# Create provider with small message limit
|
||||
redis_provider = RedisHistoryProvider(
|
||||
source_id="redis_limited_chat",
|
||||
redis_url="redis://localhost:6379",
|
||||
max_messages=3, # Keep only 3 most recent messages
|
||||
)
|
||||
|
||||
agent = OpenAIChatClient().as_agent(
|
||||
name="LimitBot",
|
||||
instructions="You are a helpful assistant with limited memory.",
|
||||
context_providers=[redis_provider],
|
||||
)
|
||||
|
||||
session = agent.create_session()
|
||||
|
||||
# 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, session=session)
|
||||
print(f"Agent: {response.text}")
|
||||
|
||||
print("Done\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Run all Redis history provider examples."""
|
||||
print("Redis History Provider 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:
|
||||
# Run all examples
|
||||
await example_manual_memory_store()
|
||||
await example_user_session_management()
|
||||
await example_conversation_persistence()
|
||||
await example_session_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())
|
||||
@@ -1,322 +0,0 @@
|
||||
# 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())
|
||||
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import AgentSession
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
"""
|
||||
Session Suspend and Resume Example
|
||||
|
||||
This sample demonstrates how to suspend and resume conversation sessions, comparing
|
||||
service-managed sessions (Azure AI) with in-memory sessions (OpenAI) for persistent
|
||||
conversation state across sessions.
|
||||
"""
|
||||
|
||||
|
||||
async def suspend_resume_service_managed_session() -> None:
|
||||
"""Demonstrates how to suspend and resume a service-managed session."""
|
||||
print("=== Suspend-Resume Service-Managed Session ===")
|
||||
|
||||
# AzureAIAgentClient supports service-managed sessions.
|
||||
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 session for the agent conversation.
|
||||
session = agent.create_session()
|
||||
|
||||
# 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, session=session)}\n")
|
||||
|
||||
# Serialize the session state, so it can be stored for later use.
|
||||
serialized_session = session.to_dict()
|
||||
|
||||
# The session can now be saved to a database, file, or any other storage mechanism and loaded again later.
|
||||
print(f"Serialized session: {serialized_session}\n")
|
||||
|
||||
# Deserialize the session state after loading from storage.
|
||||
resumed_session = AgentSession.from_dict(serialized_session)
|
||||
|
||||
# Respond to user input.
|
||||
query = "What do you remember about me?"
|
||||
print(f"User: {query}")
|
||||
print(f"Agent: {await agent.run(query, session=resumed_session)}\n")
|
||||
|
||||
|
||||
async def suspend_resume_in_memory_session() -> None:
|
||||
"""Demonstrates how to suspend and resume an in-memory session."""
|
||||
print("=== Suspend-Resume In-Memory Session ===")
|
||||
|
||||
# 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 session for the agent conversation.
|
||||
session = agent.create_session()
|
||||
|
||||
# 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, session=session)}\n")
|
||||
|
||||
# Serialize the session state, so it can be stored for later use.
|
||||
serialized_session = session.to_dict()
|
||||
|
||||
# The session can now be saved to a database, file, or any other storage mechanism and loaded again later.
|
||||
print(f"Serialized session: {serialized_session}\n")
|
||||
|
||||
# Deserialize the session state after loading from storage.
|
||||
resumed_session = AgentSession.from_dict(serialized_session)
|
||||
|
||||
# Respond to user input.
|
||||
query = "What do you remember about me?"
|
||||
print(f"User: {query}")
|
||||
print(f"Agent: {await agent.run(query, session=resumed_session)}\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== Suspend-Resume Session Examples ===")
|
||||
await suspend_resume_service_managed_session()
|
||||
await suspend_resume_in_memory_session()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,92 +0,0 @@
|
||||
# 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())
|
||||
Reference in New Issue
Block a user