Python: restructure: Python samples into progressive 01-05 layout (#3862)

* restructure: Python samples into progressive 01-05 layout

- 01-get-started/: 6 numbered steps (hello agent → hosting)
- 02-agents/: all agent concept samples (tools, middleware, providers, etc.)
- 03-workflows/: ALL existing workflow samples preserved as-is
- 04-hosting/: azure-functions, durabletask, a2a
- 05-end-to-end/: demos, evaluation, hosted agents
- Old files moved to _to_delete/ for review
- Added AGENTS.md with structure documentation
- autogen-migration/ and semantic-kernel-migration/ preserved at root

* fix: switch to AzureOpenAI Foundry, fix CI failures

- Switch all 01-get-started samples to AzureOpenAIResponsesClient with
  Azure AI Foundry project endpoint (AZURE_AI_PROJECT_ENDPOINT +
  AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME + AzureCliCredential)
- Add _to_delete/ and 05-end-to-end/ to pyrightconfig.samples.json excludes
- Fix test paths in packages/ that referenced old getting_started/ dirs:
  durabletask conftest + streaming test, azurefunctions conftest,
  devui conftest + capture_messages + openai_sdk_integration
- Fix workflow_as_agent_human_in_the_loop.py import (sibling import)
- Update hosting READMEs and tool comment paths
- Replace root README.md with new structure overview
- Update AGENTS.md to document Azure OpenAI Foundry as default provider

* cleanup: remove _to_delete folder, copy resource files to active dirs

All files in _to_delete/ were either:
- Exact duplicates of files in the new structure (240 files)
- Same file with only comment path updates (100 files)
- One import-fix diff (workflow_as_agent_human_in_the_loop.py)
- One superseded minimal_sample.py

Resource files (sample.pdf, countries.json, employees.pdf, weather.json)
copied to 02-agents/sample_assets/ and 02-agents/resources/ since active
samples reference them.

* fix: address PR review comments, centralize resources, remove root duplicates

- Fix type annotation in 04_memory.py (string union -> proper types)
- Fix old sample paths in observability files
- Fix grammar/spelling in observability samples
- Move sample_assets/ and resources/ to shared/ folder
- Remove 8 duplicate observability files from 02-agents root
- Update resource path references in multimodal_input and provider samples

* fix: update broken links from old getting_started paths to new structure

- Update relative paths in READMEs: getting_started/ → 01-get-started/,
  02-agents/, 03-workflows/, 04-hosting/, 05-end-to-end/
- Fix absolute GitHub URLs in package READMEs
- Fix broken link in ollama package README

* fix: convert absolute GitHub URLs to relative paths for link checker

Absolute URLs to python/samples/ on main branch 404 until PR merges.
Converted to relative paths that linkspector can verify locally.

* fix: update link for handoff sample moved to orchestrations/

* fix: update chatkit-integration README path from demos/ to 05-end-to-end/

* fix: update broken links in orchestrations README to match flat directory structure
This commit is contained in:
Eduard van Valkenburg
2026-02-12 17:36:36 +00:00
committed by GitHub
parent 69dcfe31ee
commit a2856d3b92
536 changed files with 3816 additions and 1632 deletions
@@ -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 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,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())
@@ -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())