chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,56 @@
# Conversation & Session Management Samples
These samples demonstrate different approaches to managing conversation history and session state in Agent Framework.
## Samples
| File | Description |
|------|-------------|
| [`suspend_resume_session.py`](suspend_resume_session.py) | Suspend and resume conversation sessions, comparing service-managed sessions (Azure AI Foundry) with in-memory sessions (OpenAI). |
| [`custom_history_provider.py`](custom_history_provider.py) | Implement a custom history provider by extending `HistoryProvider`, enabling conversation persistence in your preferred storage backend. |
| [`file_history_provider.py`](file_history_provider.py) | Use the experimental `FileHistoryProvider` with `FoundryChatClient` and a function tool so the local JSON Lines file shows the full tool-calling loop. |
| [`file_history_provider_conversation_persistence.py`](file_history_provider_conversation_persistence.py) | Persist a tool-driven weather conversation with `FileHistoryProvider`, inspect the stored JSONL records, and continue with another city. |
| [`cosmos_history_provider.py`](cosmos_history_provider.py) | Use Azure Cosmos DB as a history provider for durable conversation storage with `CosmosHistoryProvider`. |
| [`cosmos_history_provider_conversation_persistence.py`](cosmos_history_provider_conversation_persistence.py) | Persist and resume conversations across application restarts using `CosmosHistoryProvider` — serialize session state, restore it, and continue with full Cosmos DB history. |
| [`cosmos_history_provider_messages.py`](cosmos_history_provider_messages.py) | Direct message history operations — retrieve stored messages as a transcript, clear session history, and verify data deletion. |
| [`cosmos_history_provider_sessions.py`](cosmos_history_provider_sessions.py) | Multi-session and multi-tenant management — per-tenant session isolation, `list_sessions()` to enumerate, switch between sessions, and resume specific conversations. |
| [`redis_history_provider.py`](redis_history_provider.py) | Use Redis as a history provider for persistent conversation history storage across sessions. |
## Prerequisites
**For `suspend_resume_session.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint (service-managed session)
- `FOUNDRY_MODEL`: The Foundry model deployment name
- `OPENAI_API_KEY`: Your OpenAI API key (in-memory session)
- Azure CLI authentication (`az login`)
**For `custom_history_provider.py`:**
- `OPENAI_API_KEY`: Your OpenAI API key
**For `file_history_provider.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: The Foundry model deployment name
- Azure CLI authentication (`az login`)
- The sample writes plaintext JSONL conversation logs to disk; use a trusted
local directory and avoid treating the history files as secure secret storage
**For `file_history_provider_conversation_persistence.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: The Foundry model deployment name
- Azure CLI authentication (`az login`)
- The sample writes plaintext JSONL conversation logs to disk; use a trusted
local directory and avoid treating the history files as secure secret storage
**For Cosmos DB samples (`cosmos_history_provider*.py`):**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
- `FOUNDRY_MODEL`: The Foundry model deployment name
- `AZURE_COSMOS_ENDPOINT`: Your Azure Cosmos DB account endpoint
- `AZURE_COSMOS_DATABASE_NAME`: The database that stores conversation history
- `AZURE_COSMOS_CONTAINER_NAME`: The container that stores conversation history
- Either `AZURE_COSMOS_KEY` or Azure CLI authentication (`az login`)
**For `redis_history_provider.py`:**
- `OPENAI_API_KEY`: Your OpenAI API key
- A running Redis server — default URL is `redis://localhost:6379`
- Override via the `REDIS_URL` environment variable for remote or authenticated instances
- Quickstart with Docker: `docker run -d --name redis-stack -p 6379:6379 redis/redis-stack-server:latest`
@@ -0,0 +1,98 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from agent_framework import Agent
from agent_framework.azure import CosmosHistoryProvider
from agent_framework.foundry import FoundryChatClient
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file.
load_dotenv()
"""
This sample demonstrates CosmosHistoryProvider as an agent history provider.
Key components:
- FoundryChatClient configured with an Azure AI project endpoint
- CosmosHistoryProvider configured for Cosmos DB-backed message history
- Provider-configured container name with session_id as partition key
Environment variables:
FOUNDRY_PROJECT_ENDPOINT
FOUNDRY_MODEL
AZURE_COSMOS_ENDPOINT
AZURE_COSMOS_DATABASE_NAME
AZURE_COSMOS_CONTAINER_NAME
Optional:
AZURE_COSMOS_KEY
"""
async def main() -> None:
"""Run the Cosmos history provider sample with an Agent."""
project_endpoint = os.getenv("FOUNDRY_PROJECT_ENDPOINT")
model = os.getenv("FOUNDRY_MODEL")
cosmos_endpoint = os.getenv("AZURE_COSMOS_ENDPOINT")
cosmos_database_name = os.getenv("AZURE_COSMOS_DATABASE_NAME")
cosmos_container_name = os.getenv("AZURE_COSMOS_CONTAINER_NAME")
cosmos_key = os.getenv("AZURE_COSMOS_KEY")
if (
not project_endpoint
or not model
or not cosmos_endpoint
or not cosmos_database_name
or not cosmos_container_name
):
print(
"Please set FOUNDRY_PROJECT_ENDPOINT, FOUNDRY_MODEL, "
"AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, and AZURE_COSMOS_CONTAINER_NAME."
)
return
# 1. Create an Azure credential and a CosmosHistoryProvider for agent context
async with (
AzureCliCredential() as credential,
CosmosHistoryProvider(
endpoint=cosmos_endpoint,
database_name=cosmos_database_name,
container_name=cosmos_container_name,
credential=cosmos_key or credential,
) as history_provider,
# 2. Create an agent that uses Cosmos for persisted conversation history.
Agent(
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=model,
credential=credential,
),
name="CosmosHistoryAgent",
instructions="You are a helpful assistant that remembers prior turns.",
context_providers=[history_provider],
default_options={"store": False},
) as agent,
):
# 3. Create a session (session_id is used as the partition key).
session = agent.create_session()
# 4. Run a multi-turn conversation; history is persisted by CosmosHistoryProvider.
response1 = await agent.run("My name is Ada and I enjoy distributed systems.", session=session)
print(f"Assistant: {response1.text}")
response2 = await agent.run("What do you remember about me?", session=session)
print(f"Assistant: {response2.text}")
print(f"Container: {history_provider.container_name}")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Assistant: Nice to meet you, Ada! Distributed systems are a fascinating area.
Assistant: You told me your name is Ada and that you enjoy distributed systems.
Container: <AZURE_COSMOS_CONTAINER_NAME>
"""
@@ -0,0 +1,163 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: T201
import asyncio
import os
from agent_framework import Agent, AgentSession
from agent_framework.foundry import FoundryChatClient
from agent_framework_azure_cosmos import CosmosHistoryProvider
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file.
load_dotenv()
"""
This sample demonstrates persisting and resuming conversations across application
restarts using CosmosHistoryProvider as the persistent backend.
Key components:
- Phase 1: Run a conversation and serialize the session with session.to_dict()
- Phase 2: Simulate an app restart — create new provider and agent instances,
restore the session with AgentSession.from_dict(), and continue the conversation
- Cosmos DB reloads the full message history, so the agent remembers everything
Environment variables:
FOUNDRY_PROJECT_ENDPOINT
FOUNDRY_MODEL
AZURE_COSMOS_ENDPOINT
AZURE_COSMOS_DATABASE_NAME
AZURE_COSMOS_CONTAINER_NAME
Optional:
AZURE_COSMOS_KEY
"""
async def main() -> None:
"""Run the conversation persistence sample."""
project_endpoint = os.getenv("FOUNDRY_PROJECT_ENDPOINT")
model = os.getenv("FOUNDRY_MODEL")
cosmos_endpoint = os.getenv("AZURE_COSMOS_ENDPOINT")
cosmos_database_name = os.getenv("AZURE_COSMOS_DATABASE_NAME")
cosmos_container_name = os.getenv("AZURE_COSMOS_CONTAINER_NAME")
cosmos_key = os.getenv("AZURE_COSMOS_KEY")
if (
not project_endpoint
or not model
or not cosmos_endpoint
or not cosmos_database_name
or not cosmos_container_name
):
print(
"Please set FOUNDRY_PROJECT_ENDPOINT, FOUNDRY_MODEL, "
"AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, and AZURE_COSMOS_CONTAINER_NAME."
)
return
# ── Phase 1: Initial conversation ──
print("=== Phase 1: Initial conversation ===\n")
async with (
AzureCliCredential() as credential,
CosmosHistoryProvider(
endpoint=cosmos_endpoint,
database_name=cosmos_database_name,
container_name=cosmos_container_name,
credential=cosmos_key or credential,
) as history_provider,
Agent(
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=model,
credential=credential,
),
name="PersistentAgent",
instructions="You are a helpful assistant that remembers prior turns.",
context_providers=[history_provider],
default_options={"store": False},
) as agent,
):
session = agent.create_session()
response1 = await agent.run("My name is Ada. I'm building a distributed database in Rust.", session=session)
print("User: My name is Ada. I'm building a distributed database in Rust.")
print(f"Assistant: {response1.text}\n")
response2 = await agent.run("The hardest part is the consensus algorithm.", session=session)
print("User: The hardest part is the consensus algorithm.")
print(f"Assistant: {response2.text}\n")
serialized_session = session.to_dict()
print(f"Session serialized. Session ID: {session.session_id}")
# ── Phase 2: Simulate app restart ──
print("\n=== Phase 2: Resuming after 'restart' ===\n")
async with (
AzureCliCredential() as credential,
CosmosHistoryProvider(
endpoint=cosmos_endpoint,
database_name=cosmos_database_name,
container_name=cosmos_container_name,
credential=cosmos_key or credential,
) as history_provider,
Agent(
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=model,
credential=credential,
),
name="PersistentAgent",
instructions="You are a helpful assistant that remembers prior turns.",
context_providers=[history_provider],
default_options={"store": False},
) as agent,
):
restored_session = AgentSession.from_dict(serialized_session)
print(f"Session restored. Session ID: {restored_session.session_id}\n")
response3 = await agent.run("What was I working on and what was the challenge?", session=restored_session)
print("User: What was I working on and what was the challenge?")
print(f"Assistant: {response3.text}\n")
messages = await history_provider.get_messages(restored_session.session_id)
print(f"Messages stored in Cosmos DB: {len(messages)}")
for i, msg in enumerate(messages, 1):
print(f" {i}. [{msg.role}] {msg.text[:80]}...")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
=== Phase 1: Initial conversation ===
User: My name is Ada. I'm building a distributed database in Rust.
Assistant: That sounds like a great project, Ada! Rust is an excellent choice for ...
User: The hardest part is the consensus algorithm.
Assistant: Consensus algorithms can be tricky! Are you looking at Raft, Paxos, or ...
Session serialized. Session ID: <session-uuid>
=== Phase 2: Resuming after 'restart' ===
Session restored. Session ID: <session-uuid>
User: What was I working on and what was the challenge?
Assistant: You told me you're building a distributed database in Rust and that the hardest
part is the consensus algorithm.
Messages stored in Cosmos DB: 6
1. [user] My name is Ada. I'm building a distributed database in Rust....
2. [assistant] That sounds like a great project, Ada! Rust is an excellent ch...
3. [user] The hardest part is the consensus algorithm....
4. [assistant] Consensus algorithms can be tricky! Are you looking at Raft, Pa...
5. [user] What was I working on and what was the challenge?...
6. [assistant] You told me you're building a distributed database in Rust and ...
"""
@@ -0,0 +1,157 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: T201
import asyncio
import os
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from agent_framework_azure_cosmos import CosmosHistoryProvider
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file.
load_dotenv()
"""
This sample demonstrates direct message history operations using
CosmosHistoryProvider — retrieving, displaying, and clearing stored messages.
Key components:
- get_messages(session_id): Retrieve all stored messages as a chat transcript
- clear(session_id): Delete all messages for a session (e.g., GDPR compliance)
- Verifying that history is empty after clearing
- Running a new conversation in the same session after clearing
Environment variables:
FOUNDRY_PROJECT_ENDPOINT
FOUNDRY_MODEL
AZURE_COSMOS_ENDPOINT
AZURE_COSMOS_DATABASE_NAME
AZURE_COSMOS_CONTAINER_NAME
Optional:
AZURE_COSMOS_KEY
"""
async def main() -> None:
"""Run the messages history sample."""
project_endpoint = os.getenv("FOUNDRY_PROJECT_ENDPOINT")
model = os.getenv("FOUNDRY_MODEL")
cosmos_endpoint = os.getenv("AZURE_COSMOS_ENDPOINT")
cosmos_database_name = os.getenv("AZURE_COSMOS_DATABASE_NAME")
cosmos_container_name = os.getenv("AZURE_COSMOS_CONTAINER_NAME")
cosmos_key = os.getenv("AZURE_COSMOS_KEY")
if (
not project_endpoint
or not model
or not cosmos_endpoint
or not cosmos_database_name
or not cosmos_container_name
):
print(
"Please set FOUNDRY_PROJECT_ENDPOINT, FOUNDRY_MODEL, "
"AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, and AZURE_COSMOS_CONTAINER_NAME."
)
return
async with (
AzureCliCredential() as credential,
CosmosHistoryProvider(
endpoint=cosmos_endpoint,
database_name=cosmos_database_name,
container_name=cosmos_container_name,
credential=cosmos_key or credential,
) as history_provider,
Agent(
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=model,
credential=credential,
),
name="HistoryAgent",
instructions="You are a helpful assistant that remembers prior turns.",
context_providers=[history_provider],
default_options={"store": False},
) as agent,
):
session = agent.create_session()
session_id = session.session_id
# 1. Have a multi-turn conversation.
print("=== Building a conversation ===\n")
queries = [
"Hi! My favorite programming language is Python.",
"I also enjoy hiking in the mountains on weekends.",
"What do you know about me so far?",
]
for query in queries:
response = await agent.run(query, session=session)
print(f"User: {query}")
print(f"Assistant: {response.text}\n")
# 2. Retrieve and display the full message history as a transcript.
print("=== Chat transcript from Cosmos DB ===\n")
messages = await history_provider.get_messages(session_id)
print(f"Total messages stored: {len(messages)}\n")
for i, msg in enumerate(messages, 1):
print(f" {i}. [{msg.role}] {msg.text[:100]}")
# 3. Clear the session history.
print("\n=== Clearing session history ===\n")
await history_provider.clear(session_id)
print(f"Cleared all messages for session: {session_id}")
# 4. Verify history is empty.
remaining = await history_provider.get_messages(session_id)
print(f"Messages after clear: {len(remaining)}")
# 5. Start a fresh conversation in the same session — agent has no memory.
print("\n=== Fresh conversation (same session, no memory) ===\n")
response = await agent.run("What do you know about me?", session=session)
print("User: What do you know about me?")
print(f"Assistant: {response.text}")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
=== Building a conversation ===
User: Hi! My favorite programming language is Python.
Assistant: That's great! Python is a wonderful language. What do you like most about it?
User: I also enjoy hiking in the mountains on weekends.
Assistant: Hiking sounds lovely! Do you have a favorite trail or mountain range?
User: What do you know about me so far?
Assistant: You love Python as your favorite programming language and enjoy hiking in the mountains on weekends.
=== Chat transcript from Cosmos DB ===
Total messages stored: 6
1. [user] Hi! My favorite programming language is Python.
2. [assistant] That's great! Python is a wonderful language. What do you like most about it?
3. [user] I also enjoy hiking in the mountains on weekends.
4. [assistant] Hiking sounds lovely! Do you have a favorite trail or mountain range?
5. [user] What do you know about me so far?
6. [assistant] You love Python as your favorite programming language and enjoy hiking ...
=== Clearing session history ===
Cleared all messages for session: <session-uuid>
Messages after clear: 0
=== Fresh conversation (same session, no memory) ===
User: What do you know about me?
Assistant: I don't have any information about you yet. Feel free to share anything you'd like!
"""
@@ -0,0 +1,193 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: T201
import asyncio
import os
from agent_framework import Agent
from agent_framework.foundry import FoundryChatClient
from agent_framework_azure_cosmos import CosmosHistoryProvider
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file.
load_dotenv()
"""
This sample demonstrates multi-session and multi-tenant management using
CosmosHistoryProvider. Each tenant (user) gets isolated conversation sessions
stored in the same Cosmos DB container, partitioned by session_id.
Key components:
- Per-tenant session isolation using prefixed session IDs
- list_sessions(): Enumerate all stored sessions across tenants
- Switching between sessions for different users
- Resuming a specific user's session — verifying data isolation
Environment variables:
FOUNDRY_PROJECT_ENDPOINT
FOUNDRY_MODEL
AZURE_COSMOS_ENDPOINT
AZURE_COSMOS_DATABASE_NAME
AZURE_COSMOS_CONTAINER_NAME
Optional:
AZURE_COSMOS_KEY
"""
async def main() -> None:
"""Run the session management sample."""
project_endpoint = os.getenv("FOUNDRY_PROJECT_ENDPOINT")
model = os.getenv("FOUNDRY_MODEL")
cosmos_endpoint = os.getenv("AZURE_COSMOS_ENDPOINT")
cosmos_database_name = os.getenv("AZURE_COSMOS_DATABASE_NAME")
cosmos_container_name = os.getenv("AZURE_COSMOS_CONTAINER_NAME")
cosmos_key = os.getenv("AZURE_COSMOS_KEY")
if (
not project_endpoint
or not model
or not cosmos_endpoint
or not cosmos_database_name
or not cosmos_container_name
):
print(
"Please set FOUNDRY_PROJECT_ENDPOINT, FOUNDRY_MODEL, "
"AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, and AZURE_COSMOS_CONTAINER_NAME."
)
return
async with (
AzureCliCredential() as credential,
CosmosHistoryProvider(
endpoint=cosmos_endpoint,
database_name=cosmos_database_name,
container_name=cosmos_container_name,
credential=cosmos_key or credential,
) as history_provider,
Agent(
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=model,
credential=credential,
),
name="MultiTenantAgent",
instructions="You are a helpful assistant that remembers prior turns.",
context_providers=[history_provider],
default_options={"store": False},
) as agent,
):
# 1. Tenant "alice" starts a conversation about travel.
print("=== Tenant: Alice — Travel conversation ===\n")
alice_session = agent.create_session(session_id="tenant-alice-session-1")
response = await agent.run("Hi! I'm planning a trip to Italy. I love Renaissance art.", session=alice_session)
print("Alice: I'm planning a trip to Italy. I love Renaissance art.")
print(f"Assistant: {response.text}\n")
response = await agent.run("Which museums should I visit in Florence?", session=alice_session)
print("Alice: Which museums should I visit in Florence?")
print(f"Assistant: {response.text}\n")
# 2. Tenant "bob" starts a separate conversation about cooking.
print("=== Tenant: Bob — Cooking conversation ===\n")
bob_session = agent.create_session(session_id="tenant-bob-session-1")
response = await agent.run("Hey! I'm learning to cook Thai food. I just made pad thai.", session=bob_session)
print("Bob: I'm learning to cook Thai food. I just made pad thai.")
print(f"Assistant: {response.text}\n")
response = await agent.run("What Thai dish should I try next?", session=bob_session)
print("Bob: What Thai dish should I try next?")
print(f"Assistant: {response.text}\n")
# 3. List all sessions stored in Cosmos DB.
print("=== Listing all sessions ===\n")
sessions = await history_provider.list_sessions()
print(f"Found {len(sessions)} session(s):")
for sid in sessions:
print(f" - {sid}")
# 4. Resume Alice's session — verify she gets her travel context back.
print("\n=== Resuming Alice's session ===\n")
alice_resumed = agent.create_session(session_id="tenant-alice-session-1")
response = await agent.run("What were we discussing?", session=alice_resumed)
print("Alice: What were we discussing?")
print(f"Assistant: {response.text}\n")
# 5. Resume Bob's session — verify he gets his cooking context back.
print("=== Resuming Bob's session ===\n")
bob_resumed = agent.create_session(session_id="tenant-bob-session-1")
response = await agent.run("What was the last dish I mentioned?", session=bob_resumed)
print("Bob: What was the last dish I mentioned?")
print(f"Assistant: {response.text}\n")
# 6. Show per-session message counts.
print("=== Per-session message counts ===\n")
alice_messages = await history_provider.get_messages("tenant-alice-session-1")
bob_messages = await history_provider.get_messages("tenant-bob-session-1")
print(f"Alice's session: {len(alice_messages)} messages")
print(f"Bob's session: {len(bob_messages)} messages")
# 7. Clean up: clear both sessions.
print("\n=== Cleaning up ===\n")
await history_provider.clear("tenant-alice-session-1")
await history_provider.clear("tenant-bob-session-1")
print("Cleared Alice's and Bob's sessions.")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
=== Tenant: Alice — Travel conversation ===
Alice: I'm planning a trip to Italy. I love Renaissance art.
Assistant: Italy is a dream for Renaissance art lovers! Florence, Rome, and Venice ...
Alice: Which museums should I visit in Florence?
Assistant: In Florence, the Uffizi Gallery is a must — it has Botticelli's Birth of Venus ...
=== Tenant: Bob — Cooking conversation ===
Bob: I'm learning to cook Thai food. I just made pad thai.
Assistant: Pad thai is a great start! How did it turn out?
Bob: What Thai dish should I try next?
Assistant: I'd suggest trying green curry or tom yum soup — both are classic Thai dishes ...
=== Listing all sessions ===
Found 2 session(s):
- tenant-alice-session-1
- tenant-bob-session-1
=== Resuming Alice's session ===
Alice: What were we discussing?
Assistant: We were discussing your trip to Italy and your love for Renaissance art ...
=== Resuming Bob's session ===
Bob: What was the last dish I mentioned?
Assistant: You mentioned pad thai — it was the dish you just made!
=== Per-session message counts ===
Alice's session: 6 messages
Bob's session: 6 messages
=== Cleaning up ===
Cleared Alice's and Bob's sessions.
"""
@@ -0,0 +1,90 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from collections.abc import Sequence
from typing import Any
from agent_framework import Agent, AgentSession, HistoryProvider, Message
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
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(HistoryProvider):
"""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 = Agent(
client=OpenAIChatClient(),
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())
@@ -0,0 +1,157 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import asyncio
import os
import tempfile
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from typing import Annotated
# Uncomment this filter to suppress the experimental FileHistoryProvider warning
# before running the sample.
# import warnings # isort: skip
# warnings.filterwarnings("ignore", message=r"\[FILE_HISTORY\].*", category=FutureWarning)
from agent_framework import Agent, FileHistoryProvider, tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from pydantic import Field
try:
import orjson # pyright: ignore[reportMissingImports]
except ImportError:
orjson = None
# Load environment variables from .env file.
load_dotenv()
"""
File History Provider
This sample demonstrates how to use the experimental `FileHistoryProvider` with
`FoundryChatClient` and a function tool so the persisted JSON Lines file shows
the tool-calling loop as well as the regular chat turns.
Environment variables:
FOUNDRY_PROJECT_ENDPOINT: Azure AI Foundry project endpoint.
FOUNDRY_MODEL: Foundry model deployment name.
Key components:
- `FileHistoryProvider`: Stores one message JSON object per line in a local
`.jsonl` file for each session.
- `lookup_weather`: A function tool that makes the persisted file show the
assistant function call and tool result lines.
- `json.dumps(..., indent=2)`: Pretty-prints selected records in the sample
output while keeping the on-disk JSONL file compact and valid.
- `USE_TEMP_DIRECTORY`: Toggle between a temporary directory and a persistent
`sessions/` folder next to this sample file.
Security posture:
- The history files are plaintext JSONL on disk, so use a trusted storage
directory and treat the files as conversation logs, not as secure secret
storage.
- Path safety checks protect the filename derived from the session id, but they
do not redact message contents or encrypt the file.
"""
USE_TEMP_DIRECTORY = False
"""When True, store JSONL files in a temporary directory for this run only."""
LOCAL_SESSIONS_DIRECTORY_NAME = "sessions"
"""Folder name used when persisting history next to this sample file."""
@tool(approval_mode="never_require")
def lookup_weather(
location: Annotated[str, Field(description="The city to look up weather for.")],
) -> str:
"""Return a deterministic weather report for a city."""
weather_reports = {
"Seattle": "Seattle is rainy with a high of 13C.",
"Amsterdam": "Amsterdam is cloudy with a high of 16C.",
}
return weather_reports.get(location, f"{location} is sunny with a high of 20C.")
@contextmanager
def _resolve_storage_directory() -> Iterator[Path]:
"""Yield the configured storage directory for the sample run."""
if USE_TEMP_DIRECTORY:
with tempfile.TemporaryDirectory(prefix="af-file-history-") as temp_directory:
yield Path(temp_directory)
return
storage_directory = Path(__file__).resolve().parent / LOCAL_SESSIONS_DIRECTORY_NAME
storage_directory.mkdir(parents=True, exist_ok=True)
yield storage_directory
async def main() -> None:
"""Run the file history provider sample."""
with _resolve_storage_directory() as storage_directory:
print(f"Using temporary directory: {USE_TEMP_DIRECTORY}")
print(f"Storage directory: {storage_directory}\n")
# 2. Create the agent with a tool so the JSONL file includes tool-calling messages.
agent = Agent(
client=FoundryChatClient(
project_endpoint=os.getenv("FOUNDRY_PROJECT_ENDPOINT"),
model=os.getenv("FOUNDRY_MODEL"),
credential=AzureCliCredential(),
),
name="FileHistoryAgent",
instructions=(
"You are a helpful assistant, use the lookup_weather tool for weather questions and "
"answer with the tool result in one sentence."
),
tools=[lookup_weather],
# if orjson is available, use it for faster JSON serialization in the FileHistoryProvider,
# otherwise fall back to the default json module.
context_providers=[
FileHistoryProvider(
storage_directory,
dumps=orjson.dumps if orjson else None,
loads=orjson.loads if orjson else None,
)
],
default_options={"store": False},
)
# 3. Let Agent create the default UUID session id for this conversation.
session = agent.create_session()
# 4. Ask a question that triggers the weather tool.
print("=== Run with tool calling ===")
query = "Use the lookup_weather tool for Seattle and tell me the weather."
response = await agent.run(query, session=session)
print(f"User: {query}")
print(f"Assistant: {response.text}\n")
# 5. Ask a follow-up question that triggers the weather tool as well
print("=== Follow-up question ===")
query = "And what about Amsterdam?"
response = await agent.run(query, session=session)
print(f"User: {query}")
print(f"Assistant: {response.text}\n")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Using temporary directory: False
Storage directory: /path/to/samples/02-agents/conversations/sessions
=== Run with tool calling ===
User: Use the lookup_weather tool for Seattle and tell me the weather.
Assistant: <model response varies>
=== Follow-up question ===
User: And what about Amsterdam?
Assistant: <model response varies>
"""
@@ -0,0 +1,185 @@
# Copyright (c) Microsoft. All rights reserved.
# ruff: noqa: T201
from __future__ import annotations
import asyncio
import json
import tempfile
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from typing import Annotated
# Uncomment this filter to suppress the experimental FileHistoryProvider warning
# before running the sample.
# import warnings # isort: skip
# warnings.filterwarnings("ignore", message=r"\[FILE_HISTORY\].*", category=FutureWarning)
from agent_framework import Agent, FileHistoryProvider, tool
from agent_framework.foundry import FoundryChatClient
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
from pydantic import Field
try:
import orjson # pyright: ignore[reportMissingImports]
except ImportError:
orjson = None
load_dotenv()
"""
File History Provider Conversation Persistence
This sample demonstrates persisting a tool-driven conversation with the
experimental `FileHistoryProvider`, reading the stored JSONL file back from
disk, and then continuing the same conversation with another city.
Environment variables:
FOUNDRY_PROJECT_ENDPOINT: Azure AI Foundry project endpoint.
FOUNDRY_MODEL: Foundry model deployment name.
Key components:
- `FileHistoryProvider`: Stores one message JSON object per line in a local
`.jsonl` file for each session.
- `get_weather`: A function tool that makes the persisted file show the
assistant function call and tool result records.
- `json.dumps(..., indent=2)`: Pretty-prints a few persisted JSONL records
while keeping the on-disk file compact and valid.
- `load_dotenv()`: Loads `.env` values up front so the sample can stay focused
on history persistence instead of manual environment variable plumbing.
- Optional `orjson`: Uses `orjson.dumps` / `orjson.loads` automatically when
available, otherwise falls back to the standard library `json` module.
Security posture:
- The history file is plaintext JSONL on disk, so use a trusted storage
directory and treat it as conversation logging, not as secure secret storage.
- Path safety checks protect the filename derived from the session id, but they
do not redact message contents or encrypt the file.
"""
USE_TEMP_DIRECTORY = False
"""When True, store JSONL files in a temporary directory for this run only."""
LOCAL_SESSIONS_DIRECTORY_NAME = "sessions"
"""Folder name used when persisting history next to this sample file."""
@tool(approval_mode="never_require")
def get_weather(
city: Annotated[str, Field(description="The city to get the weather for.")],
) -> str:
"""Return a deterministic weather report for a city."""
weather_reports = {
"Seattle": "Seattle is rainy with a high of 13C.",
"Amsterdam": "Amsterdam is cloudy with a high of 16C.",
}
return weather_reports.get(city, f"{city} is sunny with a high of 20C.")
@contextmanager
def _resolve_storage_directory() -> Iterator[Path]:
"""Yield the configured storage directory for the sample run."""
if USE_TEMP_DIRECTORY:
with tempfile.TemporaryDirectory(prefix="af-file-history-resume-") as temp_directory:
yield Path(temp_directory)
return
storage_directory = Path(__file__).resolve().parent / LOCAL_SESSIONS_DIRECTORY_NAME
storage_directory.mkdir(parents=True, exist_ok=True)
yield storage_directory
async def main() -> None:
"""Run the file history provider conversation persistence sample."""
with _resolve_storage_directory() as storage_directory:
print(f"Using temporary directory: {USE_TEMP_DIRECTORY}")
print(f"Storage directory: {storage_directory}\n")
# 1. Create the client, history provider, and tool-enabled agent.
agent = Agent(
client=FoundryChatClient(
credential=AzureCliCredential(),
),
name="WeatherHistoryAgent",
instructions=(
"You are a helpful assistant. Use the get_weather tool for weather questions "
"and answer in one sentence using the tool result."
),
tools=[get_weather],
context_providers=[
FileHistoryProvider(
storage_directory,
dumps=orjson.dumps if orjson else None,
loads=orjson.loads if orjson else None,
)
],
default_options={"store": False},
)
# 2. Ask about the first city so the JSONL file is created on disk.
session = agent.create_session()
history_file = storage_directory / f"{session.session_id}.jsonl"
print("=== First weather question ===\n")
first_query = "Use the get_weather tool and tell me the weather in Seattle."
first_response = await agent.run(first_query, session=session)
print(f"User: {first_query}")
print(f"Assistant: {first_response.text}\n")
# 3. Read the stored JSONL records back from disk and pretty-print a few of them.
raw_lines = (await asyncio.to_thread(history_file.read_text, encoding="utf-8")).splitlines()
print(f"Stored message lines after first question: {len(raw_lines)}")
print(f"History file: {history_file}\n")
print("=== JSONL preview from disk ===\n")
for index, line in enumerate(raw_lines[:4], start=1):
print(f"Record {index}:")
print(json.dumps(json.loads(line), indent=2))
print()
# 4. Continue the same persisted conversation with another city.
print("=== Second weather question ===\n")
second_query = "Now use the get_weather tool for Amsterdam."
second_response = await agent.run(second_query, session=session)
print(f"User: {second_query}")
print(f"Assistant: {second_response.text}\n")
updated_lines = (await asyncio.to_thread(history_file.read_text, encoding="utf-8")).splitlines()
print(f"Stored message lines after second question: {len(updated_lines)}")
print(f"History file: {history_file}")
if __name__ == "__main__":
asyncio.run(main())
"""
Sample output:
Using temporary directory: False
Storage directory: /path/to/samples/02-agents/conversations/sessions
=== First weather question ===
User: Use the get_weather tool and tell me the weather in Seattle.
Assistant: <model response varies>
Stored message lines after first question: 4
History file: /path/to/samples/02-agents/conversations/sessions/<session-uuid>.jsonl
=== JSONL preview from disk ===
Record 1:
{
"type": "message",
"role": "user",
...
}
=== Second weather question ===
User: Now use the get_weather tool for Amsterdam.
Assistant: <model response varies>
Stored message lines after second question: 8
History file: /path/to/samples/02-agents/conversations/sessions/<session-uuid>.jsonl
"""
@@ -0,0 +1,270 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from uuid import uuid4
from agent_framework import Agent, AgentSession
from agent_framework.openai import OpenAIChatClient
from agent_framework.redis import RedisHistoryProvider
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
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.
"""
# Default Redis URL for local Redis Stack.
# Override via the REDIS_URL environment variable for remote or authenticated instances.
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
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_URL,
)
# Create agent with Redis history provider
agent = Agent(
client=OpenAIChatClient(),
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_URL,
max_messages=10, # Keep only last 10 messages
)
# Create agent with history provider
agent = Agent(
client=OpenAIChatClient(),
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_URL,
)
agent = Agent(
client=OpenAIChatClient(),
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_URL,
)
agent = Agent(
client=OpenAIChatClient(),
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_URL,
max_messages=3, # Keep only 3 most recent messages
)
agent = Agent(
client=OpenAIChatClient(),
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 (set REDIS_URL env var or default 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())
@@ -0,0 +1,101 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from agent_framework import Agent, AgentSession
from agent_framework.foundry import FoundryChatClient
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
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 ===")
# FoundryChatClient supports service-managed sessions.
async with (
AzureCliCredential() as credential,
Agent(
client=FoundryChatClient(credential=credential),
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 = Agent(
client=OpenAIChatCompletionClient(),
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())