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:
Eduard van Valkenburg
2026-02-12 22:00:32 +01:00
committed by GitHub
Unverified
parent 0c67dbbce5
commit 1e350ea22f
312 changed files with 6669 additions and 11423 deletions
@@ -7,10 +7,10 @@ from agent_framework.azure import AzureOpenAIResponsesClient
from azure.identity import AzureCliCredential
"""
Multi-Turn Conversations — Use AgentThread to maintain context
Multi-Turn Conversations — Use AgentSession to maintain context
This sample shows how to keep conversation history across multiple calls
by reusing the same thread object.
by reusing the same session object.
Environment variables:
AZURE_AI_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
@@ -34,15 +34,15 @@ async def main() -> None:
# </create_agent>
# <multi_turn>
# Create a thread to maintain conversation history
thread = agent.get_new_thread()
# Create a session to maintain conversation history
session = agent.create_session()
# First turn
result = await agent.run("My name is Alice and I love hiking.", thread=thread)
result = await agent.run("My name is Alice and I love hiking.", session=session)
print(f"Agent: {result}\n")
# Second turn — the agent should remember the user's name and hobby
result = await agent.run("What do you remember about me?", thread=thread)
result = await agent.run("What do you remember about me?", session=session)
print(f"Agent: {result}")
# </multi_turn>
+26 -18
View File
@@ -2,10 +2,9 @@
import asyncio
import os
from collections.abc import MutableSequence
from typing import Any
from agent_framework import Context, ContextProvider, Message
from agent_framework._sessions import AgentSession, BaseContextProvider, SessionContext
from agent_framework.azure import AzureOpenAIResponsesClient
from azure.identity import AzureCliCredential
@@ -23,28 +22,37 @@ Environment variables:
# <context_provider>
class UserNameProvider(ContextProvider):
class UserNameProvider(BaseContextProvider):
"""A simple context provider that remembers the user's name."""
def __init__(self) -> None:
super().__init__(source_id="user-name-provider")
self.user_name: str | None = None
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
async def before_run(
self,
*,
agent: Any,
session: AgentSession,
context: SessionContext,
state: dict[str, Any],
) -> None:
"""Called before each agent invocation — add extra instructions."""
if self.user_name:
return Context(instructions=f"The user's name is {self.user_name}. Always address them by name.")
return Context(instructions="You don't know the user's name yet. Ask for it politely.")
context.instructions.append(f"The user's name is {self.user_name}. Always address them by name.")
else:
context.instructions.append("You don't know the user's name yet. Ask for it politely.")
async def invoked(
async def after_run(
self,
request_messages: Message | list[Message] | None = None,
response_messages: "Message | list[Message] | None" = None,
invoke_exception: Exception | None = None,
**kwargs: Any,
*,
agent: Any,
session: AgentSession,
context: SessionContext,
state: dict[str, Any],
) -> None:
"""Called after each agent invocation — extract information."""
msgs = [request_messages] if isinstance(request_messages, Message) else list(request_messages or [])
for msg in msgs:
for msg in context.input_messages:
text = msg.text if hasattr(msg, "text") else ""
if isinstance(text, str) and "my name is" in text.lower():
# Simple extraction — production code should use structured extraction
@@ -66,22 +74,22 @@ async def main() -> None:
agent = client.as_agent(
name="MemoryAgent",
instructions="You are a friendly assistant.",
context_provider=memory,
context_providers=[memory],
)
# </create_agent>
thread = agent.get_new_thread()
session = agent.create_session()
# The provider doesn't know the user yet — it will ask for a name
result = await agent.run("Hello! What's the square root of 9?", thread=thread)
result = await agent.run("Hello! What's the square root of 9?", session=session)
print(f"Agent: {result}\n")
# Now provide the name — the provider extracts and stores it
result = await agent.run("My name is Alice", thread=thread)
result = await agent.run("My name is Alice", session=session)
print(f"Agent: {result}\n")
# Subsequent calls are personalized
result = await agent.run("What is 2 + 2?", thread=thread)
result = await agent.run("What is 2 + 2?", session=session)
print(f"Agent: {result}\n")
print(f"[Memory] Stored user name: {memory.user_name}")
@@ -33,12 +33,12 @@ async def non_streaming_polling() -> None:
"""Demonstrate non-streaming background run with polling."""
print("=== Non-Streaming Polling ===\n")
thread = agent.get_new_thread()
session = agent.create_session()
# 2. Start a background run — returns immediately.
response = await agent.run(
messages="Briefly explain the theory of relativity in two sentences.",
thread=thread,
session=session,
options={"background": True},
)
@@ -50,7 +50,7 @@ async def non_streaming_polling() -> None:
poll_count += 1
await asyncio.sleep(2)
response = await agent.run(
thread=thread,
session=session,
options={"continuation_token": response.continuation_token},
)
print(f" Poll {poll_count}: continuation_token={'set' if response.continuation_token else 'None'}")
@@ -63,14 +63,14 @@ async def streaming_with_resumption() -> None:
"""Demonstrate streaming background run with simulated interruption and resumption."""
print("=== Streaming with Resumption ===\n")
thread = agent.get_new_thread()
session = agent.create_session()
# 2. Start a streaming background run.
last_token = None
stream = agent.run(
messages="Briefly list three benefits of exercise.",
stream=True,
thread=thread,
session=session,
options={"background": True},
)
@@ -91,7 +91,7 @@ async def streaming_with_resumption() -> None:
print("Resumed stream:")
stream = agent.run(
stream=True,
thread=thread,
session=session,
options={"continuation_token": last_token},
)
async for update in stream:
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -17,7 +17,7 @@ Shows function calling capabilities and automatic assistant creation.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, "The location to get the weather for."],
@@ -160,10 +160,10 @@ async def main() -> None:
print(chunk.text, end="", flush=True)
print()
# Example: Using with threads and conversation history
print("\n--- Using Custom Chat Client with Thread ---")
# Example: Using with sessions and conversation history
print("\n--- Using Custom Chat Client with Session ---")
thread = echo_agent.get_new_thread()
session = echo_agent.create_session()
# Multiple messages in conversation
messages = [
@@ -173,16 +173,17 @@ async def main() -> None:
]
for msg in messages:
result = await echo_agent.run(msg, thread=thread)
result = await echo_agent.run(msg, session=session)
print(f"User: {msg}")
print(f"Agent: {result.messages[0].text}\n")
# Check conversation history
if thread.message_store:
thread_messages = await thread.message_store.list_messages()
print(f"Thread contains {len(thread_messages)} messages")
memory_state = session.state.get("memory", {})
session_messages = memory_state.get("messages", [])
if session_messages:
print(f"Session contains {len(session_messages)} messages")
else:
print("Thread has no message store configured")
print("Session has no messages stored")
if __name__ == "__main__":
@@ -17,7 +17,7 @@ Shows function calling capabilities and automatic assistant creation.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -17,7 +17,7 @@ Shows function calling capabilities with custom business logic.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -1,179 +0,0 @@
# Context Provider Examples
Context providers enable agents to maintain memory, retrieve relevant information, and enhance conversations with external context. The Agent Framework supports various context providers for different use cases, from simple in-memory storage to advanced persistent solutions with search capabilities.
This folder contains examples demonstrating how to use different context providers with the Agent Framework.
## Overview
Context providers implement two key methods:
- **`invoking`**: Called before the agent processes a request. Provides additional context, instructions, or retrieved information to enhance the agent's response.
- **`invoked`**: Called after the agent generates a response. Allows for storing information, updating memory, or performing post-processing.
## Examples
### Simple Context Provider
| File | Description | Installation |
|------|-------------|--------------|
| [`simple_context_provider.py`](simple_context_provider.py) | Demonstrates building a custom context provider that extracts and stores user information (name and age) from conversations. Shows how to use structured output to extract data and provide dynamic instructions based on stored context. | No additional package required - uses core `agent-framework` |
**Install:**
```bash
pip install agent-framework-azure-ai
```
### Azure AI Search
| File | Description |
|------|-------------|
| [`azure_ai_search/azure_ai_with_search_context_agentic.py`](azure_ai_search/azure_ai_with_search_context_agentic.py) | **Agentic mode** (recommended for most scenarios): Uses Knowledge Bases in Azure AI Search for query planning and multi-hop reasoning. Provides more accurate results through intelligent retrieval. Slightly slower with more token consumption. |
| [`azure_ai_search/azure_ai_with_search_context_semantic.py`](azure_ai_search/azure_ai_with_search_context_semantic.py) | **Semantic mode** (fast queries): Fast hybrid search combining vector and keyword search with semantic ranking. Best for scenarios where speed is critical. |
**Install:**
```bash
pip install agent-framework-azure-ai-search agent-framework-azure-ai
```
**Prerequisites:**
- Azure AI Search service with a search index
- Azure AI Foundry project with a model deployment
- For agentic mode: Azure OpenAI resource for Knowledge Base model calls
- Environment variables: `AZURE_SEARCH_ENDPOINT`, `AZURE_SEARCH_INDEX_NAME`, `AZURE_AI_PROJECT_ENDPOINT`
**Key Concepts:**
- **Agentic mode**: Intelligent retrieval with multi-hop reasoning, better for complex queries
- **Semantic mode**: Fast hybrid search with semantic ranking, better for simple queries and speed
### Mem0
The [mem0](mem0/) folder contains examples using Mem0, a self-improving memory layer that enables applications to have long-term memory capabilities.
| File | Description |
|------|-------------|
| [`mem0/mem0_basic.py`](mem0/mem0_basic.py) | Basic example storing and retrieving user preferences across different conversation threads. |
| [`mem0/mem0_threads.py`](mem0/mem0_threads.py) | Advanced thread scoping strategies: global scope (memories shared), per-operation scope (memories isolated), and multiple agents with different memory configurations. |
| [`mem0/mem0_oss.py`](mem0/mem0_oss.py) | Using Mem0 Open Source self-hosted version as the context provider. |
**Install:**
```bash
pip install agent-framework-mem0
```
**Prerequisites:**
- Mem0 API key from [app.mem0.ai](https://app.mem0.ai/) OR self-host [Mem0 Open Source](https://docs.mem0.ai/open-source/overview)
- For Mem0 Platform: `MEM0_API_KEY` environment variable
- For Mem0 OSS: `OPENAI_API_KEY` for embedding generation
**Key Concepts:**
- **Global Scope**: Memories shared across all conversation threads
- **Thread Scope**: Memories isolated per conversation thread
- **Memory Association**: Records can be associated with `user_id`, `agent_id`, `thread_id`, or `application_id`
See the [mem0 README](mem0/README.md) for detailed documentation.
### Redis
The [redis](redis/) folder contains examples using Redis (RediSearch) for persistent, searchable memory with full-text and optional hybrid vector search.
| File | Description |
|------|-------------|
| [`redis/redis_basics.py`](redis/redis_basics.py) | Standalone provider usage and agent integration. Demonstrates writing messages, full-text/hybrid search, persisting preferences, and tool output memory. |
| [`redis/redis_conversation.py`](redis/redis_conversation.py) | Conversational examples showing memory persistence across sessions. |
| [`redis/redis_threads.py`](redis/redis_threads.py) | Thread scoping: global scope, per-operation scope, and multiple agents with isolated memory via different `agent_id` values. |
**Install:**
```bash
pip install agent-framework-redis
```
**Prerequisites:**
- Running Redis with RediSearch (Redis Stack or managed service)
- **Docker**: `docker run --name redis -p 6379:6379 -d redis:8.0.3`
- **Redis Cloud**: [redis.io/cloud](https://redis.io/cloud/)
- **Azure Managed Redis**: [Azure quickstart](https://learn.microsoft.com/azure/redis/quickstart-create-managed-redis)
- Optional: `OPENAI_API_KEY` for vector embeddings (hybrid search)
**Key Concepts:**
- **Full-text search**: Fast keyword-based retrieval
- **Hybrid vector search**: Optional embeddings for semantic search (`vectorizer_choice="openai"` or `"hf"`)
- **Memory scoping**: Partition by `application_id`, `agent_id`, `user_id`, or `thread_id`
- **Thread scoping**: `scope_to_per_operation_thread_id=True` isolates memory per operation
See the [redis README](redis/README.md) for detailed documentation.
## Choosing a Context Provider
| Provider | Use Case | Persistence | Search | Complexity |
|----------|----------|-------------|--------|------------|
| **Simple/Custom** | Learning, prototyping, simple memory needs | No (in-memory) | No | Low |
| **Azure AI Search** | RAG, document search, enterprise knowledge bases | Yes | Hybrid + Semantic | Medium |
| **Mem0** | Long-term user memory, preferences, personalization | Yes (cloud/self-hosted) | Semantic | Low-Medium |
| **Redis** | Fast retrieval, session memory, full-text + vector search | Yes | Full-text + Hybrid | Medium |
## Common Patterns
### 1. User Preference Memory
Store and retrieve user preferences, settings, or personal information across sessions.
- **Examples**: `simple_context_provider.py`, `mem0/mem0_basic.py`, `redis/redis_basics.py`
### 2. Document Retrieval (RAG)
Retrieve relevant documents or knowledge base articles to answer questions.
- **Examples**: `azure_ai_search/azure_ai_with_search_context_*.py`
### 3. Conversation History
Maintain conversation context across multiple turns and sessions.
- **Examples**: `redis/redis_conversation.py`, `mem0/mem0_threads.py`
### 4. Thread Scoping
Isolate memory per conversation thread or share globally across threads.
- **Examples**: `mem0/mem0_threads.py`, `redis/redis_threads.py`
### 5. Multi-Agent Memory
Different agents with isolated or shared memory configurations.
- **Examples**: `mem0/mem0_threads.py`, `redis/redis_threads.py`
## Building Custom Context Providers
To create a custom context provider, implement the `ContextProvider` protocol:
```python
from agent_framework import ContextProvider, Context, Message
from collections.abc import MutableSequence, Sequence
from typing import Any
class MyContextProvider(ContextProvider):
async def invoking(
self,
messages: Message | MutableSequence[Message],
**kwargs: Any
) -> Context:
"""Provide context before the agent processes the request."""
# Return additional instructions, messages, or context
return Context(instructions="Additional instructions here")
async def invoked(
self,
request_messages: Message | Sequence[Message],
response_messages: Message | Sequence[Message] | None = None,
invoke_exception: Exception | None = None,
**kwargs: Any,
) -> None:
"""Process the response after the agent generates it."""
# Store information, update memory, etc.
pass
def serialize(self) -> str:
"""Serialize the provider state for persistence."""
return "{}"
```
See `simple_context_provider.py` for a complete example.
## Additional Resources
- [Agent Framework Documentation](https://github.com/microsoft/agent-framework)
- [Azure AI Search Documentation](https://learn.microsoft.com/azure/search/)
- [Mem0 Documentation](https://docs.mem0.ai/)
- [Redis Documentation](https://redis.io/docs/)
@@ -1,276 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
"""
This sample demonstrates how to use an AggregateContextProvider to combine multiple context providers.
The AggregateContextProvider is a convenience class that allows you to aggregate multiple
ContextProviders into a single provider. It delegates events to all providers and combines
their context before returning.
You can use this implementation as-is, or implement your own aggregation logic.
"""
import asyncio
import sys
from collections.abc import MutableSequence, Sequence
from contextlib import AsyncExitStack
from types import TracebackType
from typing import TYPE_CHECKING, Any, cast
from agent_framework import Agent, Context, ContextProvider, Message
from agent_framework.azure import AzureAIClient
from azure.identity.aio import AzureCliCredential
if TYPE_CHECKING:
from agent_framework import FunctionTool
if sys.version_info >= (3, 12):
from typing import override # type: ignore # pragma: no cover
else:
from typing_extensions import override # type: ignore[import] # pragma: no cover
if sys.version_info >= (3, 11):
from typing import Self # pragma: no cover
else:
from typing_extensions import Self # pragma: no cover
# region AggregateContextProvider
class AggregateContextProvider(ContextProvider):
"""A ContextProvider that contains multiple context providers.
It delegates events to multiple context providers and aggregates responses from those
events before returning. This allows you to combine multiple context providers into a
single provider.
Examples:
.. code-block:: python
from agent_framework import Agent
# Create multiple context providers
provider1 = CustomContextProvider1()
provider2 = CustomContextProvider2()
provider3 = CustomContextProvider3()
# Combine them using AggregateContextProvider
aggregate = AggregateContextProvider([provider1, provider2, provider3])
# Pass the aggregate to the agent
agent = Agent(client=client, name="assistant", context_provider=aggregate)
# You can also add more providers later
provider4 = CustomContextProvider4()
aggregate.add(provider4)
"""
def __init__(self, context_providers: ContextProvider | Sequence[ContextProvider] | None = None) -> None:
"""Initialize the AggregateContextProvider with context providers.
Args:
context_providers: The context provider(s) to add.
"""
if isinstance(context_providers, ContextProvider):
self.providers = [context_providers]
else:
self.providers = cast(list[ContextProvider], context_providers) or []
self._exit_stack: AsyncExitStack | None = None
def add(self, context_provider: ContextProvider) -> None:
"""Add a new context provider.
Args:
context_provider: The context provider to add.
"""
self.providers.append(context_provider)
@override
async def thread_created(self, thread_id: str | None = None) -> None:
await asyncio.gather(*[x.thread_created(thread_id) for x in self.providers])
@override
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
contexts = await asyncio.gather(*[provider.invoking(messages, **kwargs) for provider in self.providers])
instructions: str = ""
return_messages: list[Message] = []
tools: list["FunctionTool"] = []
for ctx in contexts:
if ctx.instructions:
instructions += ctx.instructions
if ctx.messages:
return_messages.extend(ctx.messages)
if ctx.tools:
tools.extend(ctx.tools)
return Context(instructions=instructions, messages=return_messages, tools=tools)
@override
async def invoked(
self,
request_messages: Message | Sequence[Message],
response_messages: Message | Sequence[Message] | None = None,
invoke_exception: Exception | None = None,
**kwargs: Any,
) -> None:
await asyncio.gather(*[
x.invoked(
request_messages=request_messages,
response_messages=response_messages,
invoke_exception=invoke_exception,
**kwargs,
)
for x in self.providers
])
@override
async def __aenter__(self) -> "Self":
"""Enter the async context manager and set up all providers.
Returns:
The AggregateContextProvider instance for chaining.
"""
self._exit_stack = AsyncExitStack()
await self._exit_stack.__aenter__()
# Enter all context providers
for provider in self.providers:
await self._exit_stack.enter_async_context(provider)
return self
@override
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
"""Exit the async context manager and clean up all providers.
Args:
exc_type: The exception type if an exception occurred, None otherwise.
exc_val: The exception value if an exception occurred, None otherwise.
exc_tb: The exception traceback if an exception occurred, None otherwise.
"""
if self._exit_stack is not None:
await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
self._exit_stack = None
# endregion
# region Example Context Providers
class TimeContextProvider(ContextProvider):
"""A simple context provider that adds time-related instructions."""
@override
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
from datetime import datetime
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return Context(instructions=f"The current date and time is: {current_time}. ")
class PersonaContextProvider(ContextProvider):
"""A context provider that adds a persona to the agent."""
def __init__(self, persona: str):
self.persona = persona
@override
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
return Context(instructions=f"Your persona: {self.persona}. ")
class PreferencesContextProvider(ContextProvider):
"""A context provider that adds user preferences."""
def __init__(self):
self.preferences: dict[str, str] = {}
@override
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
if not self.preferences:
return Context()
prefs_str = ", ".join(f"{k}: {v}" for k, v in self.preferences.items())
return Context(instructions=f"User preferences: {prefs_str}. ")
@override
async def invoked(
self,
request_messages: Message | Sequence[Message],
response_messages: Message | Sequence[Message] | None = None,
invoke_exception: Exception | None = None,
**kwargs: Any,
) -> None:
# Simple example: extract and store preferences from user messages
# In a real implementation, you might use structured extraction
msgs = [request_messages] if isinstance(request_messages, Message) else list(request_messages)
for msg in msgs:
content = msg.text if hasattr(msg, "text") else ""
# Very simple extraction - in production, use LLM-based extraction
if isinstance(content, str) and "prefer" in content.lower() and ":" in content:
parts = content.split(":")
if len(parts) >= 2:
key = parts[0].strip().lower().replace("i prefer ", "")
value = parts[1].strip()
self.preferences[key] = value
# endregion
# region Main
async def main():
"""Demonstrate using AggregateContextProvider to combine multiple providers."""
async with AzureCliCredential() as credential:
client = AzureAIClient(credential=credential)
# Create individual context providers
time_provider = TimeContextProvider()
persona_provider = PersonaContextProvider("You are a helpful and friendly AI assistant named Max.")
preferences_provider = PreferencesContextProvider()
# Combine them using AggregateContextProvider
aggregate_provider = AggregateContextProvider([
time_provider,
persona_provider,
preferences_provider,
])
# Create the agent with the aggregate provider
async with Agent(
client=client,
instructions="You are a helpful assistant.",
context_provider=aggregate_provider,
) as agent:
# Create a new thread for the conversation
thread = agent.get_new_thread()
# First message - the agent should include time and persona context
print("User: Hello! Who are you?")
result = await agent.run("Hello! Who are you?", thread=thread)
print(f"Agent: {result}\n")
# Set a preference
print("User: I prefer language: formal English")
result = await agent.run("I prefer language: formal English", thread=thread)
print(f"Agent: {result}\n")
# Ask something - the agent should now include the preference
print("User: Can you tell me a fun fact?")
result = await agent.run("Can you tell me a fun fact?", thread=thread)
print(f"Agent: {result}\n")
# Show what the aggregate provider is tracking
print(f"\nPreferences tracked: {preferences_provider.preferences}")
if __name__ == "__main__":
asyncio.run(main())
@@ -144,7 +144,7 @@ async with AzureAIAgentClient(credential=DefaultAzureCredential()) as client:
async with Agent(
client=client,
model=model_deployment,
context_provider=search_provider,
context_providers=[search_provider],
) as agent:
response = await agent.run("What information is in the knowledge base?")
```
@@ -169,7 +169,7 @@ search_provider = AzureAISearchContextProvider(
async with Agent(
client=client,
model=model_deployment,
context_provider=search_provider,
context_providers=[search_provider],
) as agent:
response = await agent.run("Analyze and compare topics across documents")
```
@@ -120,7 +120,7 @@ async def main() -> None:
"Use the provided context from the knowledge base to answer complex "
"questions that may require synthesizing information from multiple sources."
),
context_provider=search_provider,
context_providers=[search_provider],
) as agent,
):
print("=== Azure AI Agent with Search Context (Agentic Mode) ===\n")
@@ -76,7 +76,7 @@ async def main() -> None:
"You are a helpful assistant. Use the provided context from the "
"knowledge base to answer questions accurately."
),
context_provider=search_provider,
context_providers=[search_provider],
) as agent,
):
print("=== Azure AI Agent with Search Context (Semantic Mode) ===\n")
@@ -9,7 +9,7 @@ This folder contains examples demonstrating how to use the Mem0 context provider
| File | Description |
|------|-------------|
| [`mem0_basic.py`](mem0_basic.py) | Basic example of using Mem0 context provider to store and retrieve user preferences across different conversation threads. |
| [`mem0_threads.py`](mem0_threads.py) | Advanced example demonstrating different thread scoping strategies with Mem0. Covers global thread scope (memories shared across all operations), per-operation thread scope (memories isolated per thread), and multiple agents with different memory configurations for personal vs. work contexts. |
| [`mem0_sessions.py`](mem0_sessions.py) | Advanced example demonstrating different thread scoping strategies with Mem0. Covers global thread scope (memories shared across all operations), per-operation thread scope (memories isolated per thread), and multiple agents with different memory configurations for personal vs. work contexts. |
| [`mem0_oss.py`](mem0_oss.py) | Example of using the Mem0 Open Source self-hosted version as the context provider. Demonstrates setup and configuration for local deployment. |
## Prerequisites
@@ -5,11 +5,11 @@ import uuid
from agent_framework import tool
from agent_framework.azure import AzureAIAgentClient
from agent_framework.mem0 import Mem0Provider
from agent_framework.mem0 import Mem0ContextProvider
from azure.identity.aio import AzureCliCredential
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def retrieve_company_report(company_code: str, detailed: bool) -> str:
if company_code != "CNTS":
@@ -39,7 +39,7 @@ async def main() -> None:
name="FriendlyAssistant",
instructions="You are a friendly assistant.",
tools=retrieve_company_report,
context_provider=Mem0Provider(user_id=user_id),
context_providers=[Mem0ContextProvider(user_id=user_id)],
) as agent,
):
# First ask the agent to retrieve a company report with no previous context.
@@ -64,17 +64,17 @@ async def main() -> None:
print("Waiting for memories to be processed...")
await asyncio.sleep(12) # Empirically determined delay for Mem0 indexing
print("\nRequest within a new thread:")
# Create a new thread for the agent.
# The new thread has no context of the previous conversation.
thread = agent.get_new_thread()
print("\nRequest within a new session:")
# Create a new session for the agent.
# The new session has no context of the previous conversation.
session = agent.create_session()
# Since we have the mem0 component in the thread, the agent should be able to
# Since we have the mem0 component in the session, the agent should be able to
# retrieve the company report without asking for clarification, as it will
# be able to remember the user preferences from Mem0 component.
query = "Please retrieve my company report"
print(f"User: {query}")
result = await agent.run(query, thread=thread)
result = await agent.run(query, session=session)
print(f"Agent: {result}\n")
@@ -5,12 +5,12 @@ import uuid
from agent_framework import tool
from agent_framework.azure import AzureAIAgentClient
from agent_framework.mem0 import Mem0Provider
from agent_framework.mem0 import Mem0ContextProvider
from azure.identity.aio import AzureCliCredential
from mem0 import AsyncMemory
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def retrieve_company_report(company_code: str, detailed: bool) -> str:
if company_code != "CNTS":
@@ -42,7 +42,7 @@ async def main() -> None:
name="FriendlyAssistant",
instructions="You are a friendly assistant.",
tools=retrieve_company_report,
context_provider=Mem0Provider(user_id=user_id, mem0_client=local_mem0_client),
context_providers=[Mem0ContextProvider(user_id=user_id, mem0_client=local_mem0_client)],
) as agent,
):
# First ask the agent to retrieve a company report with no previous context.
@@ -60,18 +60,18 @@ async def main() -> None:
result = await agent.run(query)
print(f"Agent: {result}\n")
print("\nRequest within a new thread:")
print("\nRequest within a new session:")
# Create a new thread for the agent.
# The new thread has no context of the previous conversation.
thread = agent.get_new_thread()
# Create a new session for the agent.
# The new session has no context of the previous conversation.
session = agent.create_session()
# Since we have the mem0 component in the thread, the agent should be able to
# Since we have the mem0 component in the session, the agent should be able to
# retrieve the company report without asking for clarification, as it will
# be able to remember the user preferences from Mem0 component.
query = "Please retrieve my company report"
print(f"User: {query}")
result = await agent.run(query, thread=thread)
result = await agent.run(query, session=session)
print(f"Agent: {result}\n")
@@ -5,11 +5,11 @@ import uuid
from agent_framework import tool
from agent_framework.azure import AzureAIAgentClient
from agent_framework.mem0 import Mem0Provider
from agent_framework.mem0 import Mem0ContextProvider
from azure.identity.aio import AzureCliCredential
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_user_preferences(user_id: str) -> str:
"""Mock function to get user preferences."""
@@ -34,11 +34,11 @@ async def example_global_thread_scope() -> None:
name="GlobalMemoryAssistant",
instructions="You are an assistant that remembers user preferences across conversations.",
tools=get_user_preferences,
context_provider=Mem0Provider(
context_providers=[Mem0ContextProvider(
user_id=user_id,
thread_id=global_thread_id,
scope_to_per_operation_thread_id=False, # Share memories across all threads
),
scope_to_per_operation_thread_id=False, # Share memories across all sessions
)],
) as global_agent,
):
# Store some preferences in the global scope
@@ -47,19 +47,19 @@ async def example_global_thread_scope() -> None:
result = await global_agent.run(query)
print(f"Agent: {result}\n")
# Create a new thread - but memories should still be accessible due to global scope
new_thread = global_agent.get_new_thread()
# Create a new session - but memories should still be accessible due to global scope
new_session = global_agent.create_session()
query = "What do you know about my preferences?"
print(f"User (new thread): {query}")
result = await global_agent.run(query, thread=new_thread)
print(f"User (new session): {query}")
result = await global_agent.run(query, session=new_session)
print(f"Agent: {result}\n")
async def example_per_operation_thread_scope() -> None:
"""Example 2: Per-operation thread scope (memories isolated per thread).
"""Example 2: Per-operation thread scope (memories isolated per session).
Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single thread
throughout its lifetime. Use the same thread object for all operations with that provider.
Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single session
throughout its lifetime. Use the same session object for all operations with that provider.
"""
print("2. Per-Operation Thread Scope Example:")
print("-" * 40)
@@ -72,37 +72,37 @@ async def example_per_operation_thread_scope() -> None:
name="ScopedMemoryAssistant",
instructions="You are an assistant with thread-scoped memory.",
tools=get_user_preferences,
context_provider=Mem0Provider(
context_providers=[Mem0ContextProvider(
user_id=user_id,
scope_to_per_operation_thread_id=True, # Isolate memories per thread
),
scope_to_per_operation_thread_id=True, # Isolate memories per session
)],
) as scoped_agent,
):
# Create a specific thread for this scoped provider
dedicated_thread = scoped_agent.get_new_thread()
# Create a specific session for this scoped provider
dedicated_session = scoped_agent.create_session()
# Store some information in the dedicated thread
# Store some information in the dedicated session
query = "Remember that for this conversation, I'm working on a Python project about data analysis."
print(f"User (dedicated thread): {query}")
result = await scoped_agent.run(query, thread=dedicated_thread)
print(f"User (dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Test memory retrieval in the same dedicated thread
# Test memory retrieval in the same dedicated session
query = "What project am I working on?"
print(f"User (same dedicated thread): {query}")
result = await scoped_agent.run(query, thread=dedicated_thread)
print(f"User (same dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Store more information in the same thread
# Store more information in the same session
query = "Also remember that I prefer using pandas and matplotlib for this project."
print(f"User (same dedicated thread): {query}")
result = await scoped_agent.run(query, thread=dedicated_thread)
print(f"User (same dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Test comprehensive memory retrieval
query = "What do you know about my current project and preferences?"
print(f"User (same dedicated thread): {query}")
result = await scoped_agent.run(query, thread=dedicated_thread)
print(f"User (same dedicated session): {query}")
result = await scoped_agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
@@ -119,16 +119,16 @@ async def example_multiple_agents() -> None:
AzureAIAgentClient(credential=credential).as_agent(
name="PersonalAssistant",
instructions="You are a personal assistant that helps with personal tasks.",
context_provider=Mem0Provider(
context_providers=[Mem0ContextProvider(
agent_id=agent_id_1,
),
)],
) as personal_agent,
AzureAIAgentClient(credential=credential).as_agent(
name="WorkAssistant",
instructions="You are a work assistant that helps with professional tasks.",
context_provider=Mem0Provider(
context_providers=[Mem0ContextProvider(
agent_id=agent_id_2,
),
)],
) as work_agent,
):
# Store personal information
@@ -8,10 +8,10 @@ This folder contains an example demonstrating how to use the Redis context provi
| File | Description |
|------|-------------|
| [`azure_redis_conversation.py`](azure_redis_conversation.py) | Demonstrates conversation persistence with RedisChatMessageStore and Azure Redis with Azure AD (Entra ID) authentication using credential provider. |
| [`azure_redis_conversation.py`](azure_redis_conversation.py) | Demonstrates conversation persistence with RedisHistoryProvider and Azure Redis with Azure AD (Entra ID) authentication using credential provider. |
| [`redis_basics.py`](redis_basics.py) | Shows standalone provider usage and agent integration. Demonstrates writing messages to Redis, retrieving context via fulltext or hybrid vector search, and persisting preferences across threads. Also includes a simple tool example whose outputs are remembered. |
| [`redis_conversation.py`](redis_conversation.py) | Simple example showing conversation persistence with RedisChatMessageStore using traditional connection string authentication. |
| [`redis_threads.py`](redis_threads.py) | Demonstrates thread scoping. Includes: (1) global thread scope with a fixed `thread_id` shared across operations; (2) peroperation thread scope where `scope_to_per_operation_thread_id=True` binds memory to a single thread for the provider's lifetime; and (3) multiple agents with isolated memory via different `agent_id` values. |
| [`redis_conversation.py`](redis_conversation.py) | Simple example showing conversation persistence with RedisContextProvider using traditional connection string authentication. |
| [`redis_sessions.py`](redis_sessions.py) | Demonstrates thread scoping. Includes: (1) global thread scope with a fixed `thread_id` shared across operations; (2) peroperation thread scope where `scope_to_per_operation_thread_id=True` binds memory to a single thread for the provider's lifetime; and (3) multiple agents with isolated memory via different `agent_id` values. |
## Prerequisites
@@ -1,9 +1,9 @@
# Copyright (c) Microsoft. All rights reserved.
"""Azure Managed Redis Chat Message Store with Azure AD Authentication
"""Azure Managed Redis History Provider with Azure AD Authentication
This example demonstrates how to use Azure Managed Redis with Azure AD authentication
to persist conversational details using RedisChatMessageStore.
to persist conversational details using RedisHistoryProvider.
Requirements:
- Azure Managed Redis instance with Azure AD authentication enabled
@@ -22,7 +22,7 @@ import asyncio
import os
from agent_framework.openai import OpenAIChatClient
from agent_framework.redis import RedisChatMessageStore
from agent_framework.redis import RedisHistoryProvider
from azure.identity.aio import AzureCliCredential
from redis.credentials import CredentialProvider
@@ -60,28 +60,27 @@ async def main() -> None:
azure_credential = AzureCliCredential()
credential_provider = AzureCredentialProvider(azure_credential, user_object_id)
thread_id = "azure_test_thread"
session_id = "azure_test_session"
# Factory for creating Azure Redis chat message store
def chat_message_store_factory():
return RedisChatMessageStore(
credential_provider=credential_provider,
host=redis_host,
port=10000,
ssl=True,
thread_id=thread_id,
key_prefix="chat_messages",
max_messages=100,
)
# Create Azure Redis history provider
history_provider = RedisHistoryProvider(
credential_provider=credential_provider,
host=redis_host,
port=10000,
ssl=True,
thread_id=session_id,
key_prefix="chat_messages",
max_messages=100,
)
# Create chat client
client = OpenAIChatClient()
# Create agent with Azure Redis store
# Create agent with Azure Redis history provider
agent = client.as_agent(
name="AzureRedisAssistant",
instructions="You are a helpful assistant.",
chat_message_store_factory=chat_message_store_factory,
context_providers=[history_provider],
)
# Conversation
@@ -32,12 +32,12 @@ import os
from agent_framework import Message, tool
from agent_framework.openai import OpenAIChatClient
from agent_framework_redis._provider import RedisProvider
from agent_framework.redis import RedisContextProvider
from redisvl.extensions.cache.embeddings import EmbeddingsCache
from redisvl.utils.vectorize import OpenAITextVectorizer
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def search_flights(origin_airport_code: str, destination_airport_code: str, detailed: bool = False) -> str:
"""Simulated flight-search tool to demonstrate tool memory.
@@ -104,7 +104,7 @@ async def main() -> None:
# Recommend default for OPENAI_CHAT_MODEL_ID is gpt-4o-mini
# We attach an embedding vectorizer so the provider can perform hybrid (text + vector)
# retrieval. If you prefer text-only retrieval, instantiate RedisProvider without the
# retrieval. If you prefer text-only retrieval, instantiate RedisContextProvider without the
# 'vectorizer' and vector_* parameters.
vectorizer = OpenAITextVectorizer(
model="text-embedding-ada-002",
@@ -114,7 +114,7 @@ async def main() -> None:
# The provider manages persistence and retrieval. application_id/agent_id/user_id
# scope data for multi-tenant separation; thread_id (set later) narrows to a
# specific conversation.
provider = RedisProvider(
provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_basics",
application_id="matrix_of_kermits",
@@ -133,21 +133,27 @@ async def main() -> None:
Message("system", ["runA CONVO: System Message"]),
]
# Declare/start a conversation/thread and write messages under 'runA'.
# Threads are logical boundaries used by the provider to group and retrieve
# conversation-specific context.
await provider.thread_created(thread_id="runA")
await provider.invoked(request_messages=messages)
# Use the provider's before_run/after_run API to store and retrieve messages.
# In practice, the agent handles this automatically; this shows the low-level API.
from agent_framework import AgentSession, SessionContext
# Retrieve relevant memories for a hypothetical model call. The provider uses
# the current request messages as the retrieval query and returns context to
# be injected into the model's instructions.
ctx = await provider.invoking([Message("system", ["B: Assistant Message"])])
session = AgentSession(session_id="runA")
context = SessionContext()
context.extend_messages("input", messages)
state = session.state
# Store messages via after_run
await provider.after_run(agent=None, session=session, context=context, state=state)
# Retrieve relevant memories via before_run
query_context = SessionContext()
query_context.extend_messages("input", [Message("system", ["B: Assistant Message"])])
await provider.before_run(agent=None, session=session, context=query_context, state=state)
# Inspect retrieved memories that would be injected into instructions
# (Debug-only output so you can verify retrieval works as expected.)
print("Model Invoking Result:")
print(ctx)
print("Before Run Result:")
print(query_context)
# Drop / delete the provider index in Redis
await provider.redis_index.delete()
@@ -163,7 +169,7 @@ async def main() -> None:
cache=EmbeddingsCache(name="openai_embeddings_cache", redis_url="redis://localhost:6379"),
)
# Recreate a clean index so the next scenario starts fresh
provider = RedisProvider(
provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_basics_2",
prefix="context_2",
@@ -187,7 +193,7 @@ async def main() -> None:
"Before answering, always check for stored context"
),
tools=[],
context_provider=provider,
context_providers=[provider],
)
# Teach a user preference; the agent writes this to the provider's memory
@@ -210,7 +216,7 @@ async def main() -> None:
print("\n3. Agent + provider + tool: store and recall tool-derived context")
print("-" * 40)
# Text-only provider (full-text search only). Omits vectorizer and related params.
provider = RedisProvider(
provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_basics_3",
prefix="context_3",
@@ -229,7 +235,7 @@ async def main() -> None:
"Before answering, always check for stored context"
),
tools=search_flights,
context_provider=provider,
context_providers=[provider],
)
# Invoke the tool; outputs become part of memory/context
query = "Are there any flights from new york city (jfk) to la? Give me details"
@@ -2,7 +2,7 @@
"""Redis Context Provider: Basic usage and agent integration
This example demonstrates how to use the Redis ChatMessageStoreProtocol to persist
This example demonstrates how to use the Redis context provider to persist
conversational details. Pass it as a constructor argument to create_agent.
Requirements:
@@ -18,8 +18,7 @@ import asyncio
import os
from agent_framework.openai import OpenAIChatClient
from agent_framework_redis._chat_message_store import RedisChatMessageStore
from agent_framework_redis._provider import RedisProvider
from agent_framework.redis import RedisContextProvider
from redisvl.extensions.cache.embeddings import EmbeddingsCache
from redisvl.utils.vectorize import OpenAITextVectorizer
@@ -37,9 +36,9 @@ async def main() -> None:
cache=EmbeddingsCache(name="openai_embeddings_cache", redis_url="redis://localhost:6379"),
)
thread_id = "test_thread"
session_id = "test_session"
provider = RedisProvider(
provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_conversation",
prefix="redis_conversation",
@@ -50,17 +49,9 @@ async def main() -> None:
vector_field_name="vector",
vector_algorithm="hnsw",
vector_distance_metric="cosine",
thread_id=thread_id,
thread_id=session_id,
)
def chat_message_store_factory():
return RedisChatMessageStore(
redis_url="redis://localhost:6379",
thread_id=thread_id,
key_prefix="chat_messages",
max_messages=100,
)
# Create chat client for the agent
client = OpenAIChatClient(model_id=os.getenv("OPENAI_CHAT_MODEL_ID"), api_key=os.getenv("OPENAI_API_KEY"))
# Create agent wired to the Redis context provider. The provider automatically
@@ -72,8 +63,7 @@ async def main() -> None:
"Before answering, always check for stored context"
),
tools=[],
context_provider=provider,
chat_message_store_factory=chat_message_store_factory,
context_providers=[provider],
)
# Teach a user preference; the agent writes this to the provider's memory
@@ -31,7 +31,7 @@ import os
import uuid
from agent_framework.openai import OpenAIChatClient
from agent_framework_redis._provider import RedisProvider
from agent_framework.redis import RedisContextProvider
from redisvl.extensions.cache.embeddings import EmbeddingsCache
from redisvl.utils.vectorize import OpenAITextVectorizer
@@ -51,16 +51,14 @@ async def example_global_thread_scope() -> None:
api_key=os.getenv("OPENAI_API_KEY"),
)
provider = RedisProvider(
provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_threads_global",
# overwrite_redis_index=True,
# drop_redis_index=True,
application_id="threads_demo_app",
agent_id="threads_demo_agent",
user_id="threads_demo_user",
thread_id=global_thread_id,
scope_to_per_operation_thread_id=False, # Share memories across all threads
scope_to_per_operation_thread_id=False, # Share memories across all sessions
)
agent = client.as_agent(
@@ -70,7 +68,7 @@ async def example_global_thread_scope() -> None:
"Before answering, always check for stored context containing information"
),
tools=[],
context_provider=provider,
context_providers=[provider],
)
# Store a preference in the global scope
@@ -79,11 +77,11 @@ async def example_global_thread_scope() -> None:
result = await agent.run(query)
print(f"Agent: {result}\n")
# Create a new thread - memories should still be accessible due to global scope
new_thread = agent.get_new_thread()
# Create a new session - memories should still be accessible due to global scope
new_session = agent.create_session()
query = "What technical responses do I prefer?"
print(f"User (new thread): {query}")
result = await agent.run(query, thread=new_thread)
print(f"User (new session): {query}")
result = await agent.run(query, session=new_session)
print(f"Agent: {result}\n")
# Clean up the Redis index
@@ -91,10 +89,10 @@ async def example_global_thread_scope() -> None:
async def example_per_operation_thread_scope() -> None:
"""Example 2: Per-operation thread scope (memories isolated per thread).
"""Example 2: Per-operation thread scope (memories isolated per session).
Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single thread
throughout its lifetime. Use the same thread object for all operations with that provider.
Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single session
throughout its lifetime. Use the same session object for all operations with that provider.
"""
print("2. Per-Operation Thread Scope Example:")
print("-" * 40)
@@ -110,7 +108,7 @@ async def example_per_operation_thread_scope() -> None:
cache=EmbeddingsCache(name="openai_embeddings_cache", redis_url="redis://localhost:6379"),
)
provider = RedisProvider(
provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_threads_dynamic",
# overwrite_redis_index=True,
@@ -118,7 +116,7 @@ async def example_per_operation_thread_scope() -> None:
application_id="threads_demo_app",
agent_id="threads_demo_agent",
user_id="threads_demo_user",
scope_to_per_operation_thread_id=True, # Isolate memories per thread
scope_to_per_operation_thread_id=True, # Isolate memories per session
redis_vectorizer=vectorizer,
vector_field_name="vector",
vector_algorithm="hnsw",
@@ -128,34 +126,34 @@ async def example_per_operation_thread_scope() -> None:
agent = client.as_agent(
name="ScopedMemoryAssistant",
instructions="You are an assistant with thread-scoped memory.",
context_provider=provider,
context_providers=[provider],
)
# Create a specific thread for this scoped provider
dedicated_thread = agent.get_new_thread()
# Create a specific session for this scoped provider
dedicated_session = agent.create_session()
# Store some information in the dedicated thread
# Store some information in the dedicated session
query = "Remember that for this conversation, I'm working on a Python project about data analysis."
print(f"User (dedicated thread): {query}")
result = await agent.run(query, thread=dedicated_thread)
print(f"User (dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Test memory retrieval in the same dedicated thread
# Test memory retrieval in the same dedicated session
query = "What project am I working on?"
print(f"User (same dedicated thread): {query}")
result = await agent.run(query, thread=dedicated_thread)
print(f"User (same dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Store more information in the same thread
# Store more information in the same session
query = "Also remember that I prefer using pandas and matplotlib for this project."
print(f"User (same dedicated thread): {query}")
result = await agent.run(query, thread=dedicated_thread)
print(f"User (same dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Test comprehensive memory retrieval
query = "What do you know about my current project and preferences?"
print(f"User (same dedicated thread): {query}")
result = await agent.run(query, thread=dedicated_thread)
print(f"User (same dedicated session): {query}")
result = await agent.run(query, session=dedicated_session)
print(f"Agent: {result}\n")
# Clean up the Redis index
@@ -178,7 +176,7 @@ async def example_multiple_agents() -> None:
cache=EmbeddingsCache(name="openai_embeddings_cache", redis_url="redis://localhost:6379"),
)
personal_provider = RedisProvider(
personal_provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_threads_agents",
application_id="threads_demo_app",
@@ -193,10 +191,10 @@ async def example_multiple_agents() -> None:
personal_agent = client.as_agent(
name="PersonalAssistant",
instructions="You are a personal assistant that helps with personal tasks.",
context_provider=personal_provider,
context_providers=[personal_provider],
)
work_provider = RedisProvider(
work_provider = RedisContextProvider(
redis_url="redis://localhost:6379",
index_name="redis_threads_agents",
application_id="threads_demo_app",
@@ -211,7 +209,7 @@ async def example_multiple_agents() -> None:
work_agent = client.as_agent(
name="WorkAssistant",
instructions="You are a work assistant that helps with professional tasks.",
context_provider=work_provider,
context_providers=[work_provider],
)
# Store personal information
@@ -1,10 +1,9 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from collections.abc import MutableSequence, Sequence
from typing import Any
from agent_framework import Agent, Context, ContextProvider, Message, SupportsChatGetResponse
from agent_framework import Agent, AgentSession, BaseContextProvider, SessionContext, SupportsChatGetResponse
from agent_framework.azure import AzureAIClient
from azure.identity.aio import AzureCliCredential
from pydantic import BaseModel
@@ -15,13 +14,13 @@ class UserInfo(BaseModel):
age: int | None = None
class UserInfoMemory(ContextProvider):
class UserInfoMemory(BaseContextProvider):
def __init__(self, client: SupportsChatGetResponse, user_info: UserInfo | None = None, **kwargs: Any):
"""Create the memory.
If you pass in kwargs, they will be attempted to be used to create a UserInfo object.
"""
super().__init__("user-info-memory")
self._chat_client = client
if user_info:
self.user_info = user_info
@@ -30,14 +29,16 @@ class UserInfoMemory(ContextProvider):
else:
self.user_info = UserInfo()
async def invoked(
async def after_run(
self,
request_messages: Message | Sequence[Message],
response_messages: Message | Sequence[Message] | None = None,
invoke_exception: Exception | None = None,
**kwargs: Any,
*,
agent: Any,
session: AgentSession | None,
context: SessionContext,
state: dict[str, Any],
) -> None:
"""Extract user information from messages after each agent call."""
request_messages = context.get_messages()
# Check if we need to extract user info from user messages
user_messages = [msg for msg in request_messages if hasattr(msg, "role") and msg.role == "user"] # type: ignore
@@ -64,7 +65,14 @@ class UserInfoMemory(ContextProvider):
except Exception:
pass # Failed to extract, continue without updating
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
async def before_run(
self,
*,
agent: Any,
session: AgentSession | None,
context: SessionContext,
state: dict[str, Any],
) -> None:
"""Provide user information context before each agent call."""
instructions: list[str] = []
@@ -82,11 +90,11 @@ class UserInfoMemory(ContextProvider):
else:
instructions.append(f"The user's age is {self.user_info.age}.")
# Return context with additional instructions
return Context(instructions=" ".join(instructions))
# Add context with additional instructions
context.extend_instructions(self.source_id, " ".join(instructions))
def serialize(self) -> str:
"""Serialize the user info for thread persistence."""
"""Serialize the user info for session persistence."""
return self.user_info.model_dump_json()
@@ -101,21 +109,20 @@ async def main():
async with Agent(
client=client,
instructions="You are a friendly assistant. Always address the user by their name.",
context_provider=memory_provider,
context_providers=[memory_provider],
) as agent:
# Create a new thread for the conversation
thread = agent.get_new_thread()
# Create a new session for the conversation
session = agent.create_session()
print(await agent.run("Hello, what is the square root of 9?", thread=thread))
print(await agent.run("My name is Ruaidhrí", thread=thread))
print(await agent.run("I am 20 years old", thread=thread))
print(await agent.run("Hello, what is the square root of 9?", session=session))
print(await agent.run("My name is Ruaidhrí", session=session))
print(await agent.run("I am 20 years old", session=session))
# Access the memory component via the thread's get_service method and inspect the memories
user_info_memory = thread.context_provider.providers[0] # type: ignore
if user_info_memory:
# Access the memory component and inspect the memories
if memory_provider:
print()
print(f"MEMORY - User Name: {user_info_memory.user_info.name}") # type: ignore
print(f"MEMORY - User Age: {user_info_memory.user_info.age}") # type: ignore
print(f"MEMORY - User Name: {memory_provider.user_info.name}")
print(f"MEMORY - User Age: {memory_provider.user_info.age}")
if __name__ == "__main__":
@@ -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())
@@ -50,7 +50,7 @@ def analyze_content(
return f"Analyzing content for: {query}"
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def summarize_document(
length: Annotated[str, "Desired summary length: 'brief', 'medium', or 'detailed'"] = "medium",
@@ -14,7 +14,7 @@ from azure.identity.aio import AzureCliCredential
from pydantic import Field
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -16,7 +16,7 @@ from agent_framework.devui import serve
from typing_extensions import Never
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
# Tool functions for the agent
@tool(approval_mode="never_require")
@@ -101,7 +101,7 @@ async def atlantis_location_filter_middleware(
await call_next()
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, "The location to get the weather for."],
@@ -32,7 +32,7 @@ with the following configuration:
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_specials() -> Annotated[str, "Returns the specials from the menu."]:
return """
@@ -54,7 +54,7 @@ Agent Middleware Execution Order:
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -37,7 +37,7 @@ The example covers:
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -34,7 +34,7 @@ from object-oriented design patterns.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -42,7 +42,7 @@ Key benefits of decorator approach:
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_current_time() -> str:
"""Get the current time."""
@@ -24,7 +24,7 @@ a helpful message for the user, preventing raw exceptions from reaching the end
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def unstable_data_service(
query: Annotated[str, Field(description="The data query to execute.")],
@@ -31,7 +31,7 @@ can be implemented as async functions that accept context and call_next paramete
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -30,7 +30,7 @@ This is useful for implementing security checks, rate limiting, or early exit co
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -39,7 +39,7 @@ it creates a custom async generator that yields the override message in chunks.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -81,7 +81,7 @@ class SessionContextContainer:
runtime_context = SessionContextContainer()
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def send_email(
to: Annotated[str, Field(description="Recipient email address")],
@@ -6,7 +6,6 @@ from typing import Annotated
from agent_framework import (
AgentContext,
ChatMessageStore,
tool,
)
from agent_framework.azure import AzureOpenAIChatClient
@@ -16,23 +15,23 @@ from pydantic import Field
"""
Thread Behavior MiddlewareTypes Example
This sample demonstrates how middleware can access and track thread state across multiple agent runs.
This sample demonstrates how middleware can access and track session state across multiple agent runs.
The example shows:
- How AgentContext.thread property behaves across multiple runs
- How middleware can access conversation history through the thread
- The timing of when thread messages are populated (before vs after call_next() call)
- How to track thread state changes across runs
- How AgentContext.session property behaves across multiple runs
- How middleware can access conversation history through the session
- The timing of when session messages are populated (before vs after call_next() call)
- How to track session state changes across runs
Key behaviors demonstrated:
1. First run: context.messages is populated, context.thread is initially empty (before call_next())
2. After call_next(): thread contains input message + response from agent
3. Second run: context.messages contains only current input, thread contains previous history
4. After call_next(): thread contains full conversation history (all previous + current messages)
1. First run: context.messages is populated, context.session is initially empty (before call_next())
2. After call_next(): session contains input message + response from agent
3. Second run: context.messages contains only current input, session contains previous history
4. After call_next(): session contains full conversation history (all previous + current messages)
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -48,28 +47,30 @@ async def thread_tracking_middleware(
context: AgentContext,
call_next: Callable[[], Awaitable[None]],
) -> None:
"""MiddlewareTypes that tracks and logs thread behavior across runs."""
thread_messages = []
if context.thread and context.thread.message_store:
thread_messages = await context.thread.message_store.list_messages()
"""MiddlewareTypes that tracks and logs session behavior across runs."""
session_message_count = 0
if context.session:
memory_state = context.session.state.get("memory", {})
session_message_count = len(memory_state.get("messages", []))
print(f"[MiddlewareTypes pre-execution] Current input messages: {len(context.messages)}")
print(f"[MiddlewareTypes pre-execution] Thread history messages: {len(thread_messages)}")
print(f"[MiddlewareTypes pre-execution] Session history messages: {session_message_count}")
# Call call_next to execute the agent
await call_next()
# Check thread state after agent execution
updated_thread_messages = []
if context.thread and context.thread.message_store:
updated_thread_messages = await context.thread.message_store.list_messages()
# Check session state after agent execution
updated_session_message_count = 0
if context.session:
memory_state = context.session.state.get("memory", {})
updated_session_message_count = len(memory_state.get("messages", []))
print(f"[MiddlewareTypes post-execution] Updated thread messages: {len(updated_thread_messages)}")
print(f"[MiddlewareTypes post-execution] Updated session messages: {updated_session_message_count}")
async def main() -> None:
"""Example demonstrating thread behavior in middleware across multiple runs."""
print("=== Thread Behavior MiddlewareTypes Example ===")
"""Example demonstrating session behavior in middleware across multiple runs."""
print("=== Session Behavior MiddlewareTypes Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -78,23 +79,21 @@ async def main() -> None:
instructions="You are a helpful weather assistant.",
tools=get_weather,
middleware=[thread_tracking_middleware],
# Configure agent with message store factory to persist conversation history
chat_message_store_factory=ChatMessageStore,
)
# Create a thread that will persist messages between runs
thread = agent.get_new_thread()
# Create a session that will persist messages between runs
session = agent.create_session()
print("\nFirst Run:")
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1.text}")
print("\nSecond Run:")
query2 = "How about in London?"
print(f"User: {query2}")
result2 = await agent.run(query2, thread=thread)
result2 = await agent.run(query2, session=session)
print(f"Agent: {result2.text}")
@@ -27,7 +27,7 @@ This approach shows how middleware can work together by sharing state within the
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -66,7 +66,7 @@ def setup_metrics():
set_meter_provider(meter_provider)
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -40,7 +40,7 @@ You can also set the environment variables instead of passing them as CLI argume
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -17,7 +17,7 @@ same observability setup function.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -46,13 +46,13 @@ async def main():
instructions="You are a weather assistant.",
id="weather-agent",
)
thread = agent.get_new_thread()
session = agent.create_session()
for question in questions:
print(f"\nUser: {question}")
print(f"{agent.name}: ", end="")
async for update in agent.run(
question,
thread=thread,
session=session,
stream=True,
):
if update.text:
@@ -41,7 +41,7 @@ dotenv.load_dotenv()
logger = logging.getLogger(__name__)
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -92,11 +92,11 @@ async def main():
instructions="You are a weather assistant.",
id="weather-agent",
)
thread = agent.get_new_thread()
session = agent.create_session()
for question in questions:
print(f"\nUser: {question}")
print(f"{agent.name}: ", end="")
async for update in agent.run(question, thread=thread, stream=True):
async for update in agent.run(question, session=session, stream=True):
if update.text:
print(update.text, end="")
@@ -29,7 +29,7 @@ for this sample to work.
dotenv.load_dotenv()
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -63,11 +63,11 @@ async def main():
instructions="You are a weather assistant.",
id="edvan-weather-agent",
)
thread = agent.get_new_thread()
session = agent.create_session()
for question in questions:
print(f"\nUser: {question}")
print(f"{agent.name}: ", end="")
async for update in agent.run(question, thread=thread, stream=True):
async for update in agent.run(question, session=session, stream=True):
if update.text:
print(update.text, end="")
@@ -31,7 +31,7 @@ output traces, logs, and metrics to the console.
SCENARIOS = ["client", "client_stream", "tool", "all"]
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -31,7 +31,7 @@ Use this approach when you need custom exporter configuration beyond what enviro
SCENARIOS = ["client", "client_stream", "tool", "all"]
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -35,7 +35,7 @@ Key Concepts:
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# See:
# samples/02-agents/tools/function_tool_with_approval.py
# samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def process_refund(order_number: Annotated[str, "Order number to process refund for"]) -> str:
"""Simulated function to process a refund for a given order number."""
@@ -14,7 +14,7 @@ This sample demonstrates using Anthropic with an agent and a single custom tool.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, "The location to get the weather for."],
@@ -66,31 +66,31 @@ async def example_with_session_persistence() -> None:
)
async with agent:
# Create a thread to maintain conversation context
thread = agent.get_new_thread()
# Create a session to maintain conversation context
session = agent.create_session()
# First query
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1.text}")
# Second query - using same thread maintains context
query2 = "How about London?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2, thread=thread)
result2 = await agent.run(query2, session=session)
print(f"Agent: {result2.text}")
# Third query - agent should remember both previous cities
query3 = "Which of the cities I asked about has better weather?"
print(f"\nUser: {query3}")
result3 = await agent.run(query3, thread=thread)
result3 = await agent.run(query3, session=session)
print(f"Agent: {result3.text}")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_session_id() -> None:
"""Resume session in new agent instance using service_thread_id."""
"""Resume session in new agent instance using service_session_id."""
print("=== Existing Session ID Example ===")
existing_session_id = None
@@ -102,15 +102,15 @@ async def example_with_existing_session_id() -> None:
)
async with agent1:
thread = agent1.get_new_thread()
session = agent1.create_session()
query1 = "What's the weather in Paris?"
print(f"User: {query1}")
result1 = await agent1.run(query1, thread=thread)
result1 = await agent1.run(query1, session=session)
print(f"Agent: {result1.text}")
# Capture the session ID for later use
existing_session_id = thread.service_thread_id
existing_session_id = session.service_session_id
print(f"Session ID: {existing_session_id}")
if existing_session_id:
@@ -123,12 +123,12 @@ async def example_with_existing_session_id() -> None:
)
async with agent2:
# Create thread with existing session ID
thread = agent2.get_new_thread(service_thread_id=existing_session_id)
# Create session with existing session ID
session = agent2.create_session(service_session_id=existing_session_id)
query2 = "What was the last city I asked about?"
print(f"User: {query2}")
result2 = await agent2.run(query2, thread=thread)
result2 = await agent2.run(query2, session=session)
print(f"Agent: {result2.text}")
print("Note: The agent continues the conversation using the session ID.\n")
@@ -20,7 +20,7 @@ This folder contains examples demonstrating different ways to create and use age
| [`azure_ai_with_code_interpreter_file_download.py`](azure_ai_with_code_interpreter_file_download.py) | Shows how to download files generated by code interpreter using the OpenAI containers API. |
| [`azure_ai_with_content_filtering.py`](azure_ai_with_content_filtering.py) | Shows how to enable content filtering (RAI policy) on Azure AI agents using `RaiConfig`. Requires creating an RAI policy in Azure AI Foundry portal first. |
| [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with a pre-existing agent by providing the agent name and version to the Azure AI client. Demonstrates agent reuse patterns for production scenarios. |
| [`azure_ai_with_existing_conversation.py`](azure_ai_with_existing_conversation.py) | Demonstrates how to use an existing conversation created on the service side with Azure AI agents. Shows two approaches: specifying conversation ID at the client level and using AgentThread with an existing conversation ID. |
| [`azure_ai_with_existing_conversation.py`](azure_ai_with_existing_conversation.py) | Demonstrates how to use an existing conversation created on the service side with Azure AI agents. Shows two approaches: specifying conversation ID at the client level and using AgentSession with an existing conversation ID. |
| [`azure_ai_with_application_endpoint.py`](azure_ai_with_application_endpoint.py) | Demonstrates calling the Azure AI application-scoped endpoint. |
| [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured `AzureAIClient` settings, including project endpoint, model deployment, and credentials rather than relying on environment variable defaults. |
| [`azure_ai_with_file_search.py`](azure_ai_with_file_search.py) | Shows how to use `AzureAIClient.get_file_search_tool()` with Azure AI agents to upload files, create vector stores, and enable agents to search through uploaded documents to answer user questions. |
@@ -31,7 +31,7 @@ This folder contains examples demonstrating different ways to create and use age
| [`azure_ai_with_search_context_agentic.py`](../../context_providers/azure_ai_search/azure_ai_with_search_context_agentic.py) | Shows how to use AzureAISearchContextProvider with agentic mode. Uses Knowledge Bases for multi-hop reasoning across documents with query planning. Recommended for most scenarios - slightly slower with more token consumption for query planning, but more accurate results. |
| [`azure_ai_with_search_context_semantic.py`](../../context_providers/azure_ai_search/azure_ai_with_search_context_semantic.py) | Shows how to use AzureAISearchContextProvider with semantic mode. Fast hybrid search with vector + keyword search and semantic ranking for RAG. Best for simple queries where speed is critical. |
| [`azure_ai_with_sharepoint.py`](azure_ai_with_sharepoint.py) | Shows how to use SharePoint grounding with Azure AI agents to search through SharePoint content and answer user questions with proper citations. Requires a SharePoint connection configured in your Azure AI project. |
| [`azure_ai_with_thread.py`](azure_ai_with_thread.py) | Demonstrates thread management with Azure AI agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
| [`azure_ai_with_session.py`](azure_ai_with_session.py) | Demonstrates session management with Azure AI agents, including automatic session creation for stateless conversations and explicit session management for maintaining conversation context across multiple interactions. |
| [`azure_ai_with_image_generation.py`](azure_ai_with_image_generation.py) | Shows how to use `AzureAIClient.get_image_generation_tool()` with Azure AI agents to generate images based on text prompts. |
| [`azure_ai_with_memory_search.py`](azure_ai_with_memory_search.py) | Shows how to use memory search functionality with Azure AI agents for conversation persistence. Demonstrates creating memory stores and enabling agents to search through conversation history. |
| [`azure_ai_with_microsoft_fabric.py`](azure_ai_with_microsoft_fabric.py) | Shows how to use Microsoft Fabric with Azure AI agents to query Fabric data sources and provide responses based on data analysis. Requires a Microsoft Fabric connection configured in your Azure AI project. |
@@ -92,4 +92,4 @@ python azure_ai_with_code_interpreter.py
# ... etc
```
The examples demonstrate various patterns for working with Azure AI agents, from basic usage to advanced scenarios like thread management and structured outputs.
The examples demonstrate various patterns for working with Azure AI agents, from basic usage to advanced scenarios like session management and structured outputs.
@@ -19,7 +19,7 @@ Shows both streaming and non-streaming responses with function tools.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -29,7 +29,7 @@ Each method returns a Agent that can be used for conversations.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ while subsequent calls with `get_agent()` reuse the latest agent version.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -19,7 +19,7 @@ This sample demonstrates usage of AzureAIProjectAgentProvider with existing conv
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -61,9 +61,9 @@ async def example_with_conversation_id() -> None:
print(f"Agent: {result.text}\n")
async def example_with_thread() -> None:
"""This example shows how to specify existing conversation ID with AgentThread."""
print("=== Azure AI Agent With Existing Conversation and Thread ===")
async def example_with_session() -> None:
"""This example shows how to specify existing conversation ID with AgentSession."""
print("=== Azure AI Agent With Existing Conversation and Session ===")
async with (
AzureCliCredential() as credential,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
@@ -81,23 +81,23 @@ async def example_with_thread() -> None:
conversation_id = conversation.id
print(f"Conversation ID: {conversation_id}")
# Create a thread with the existing ID
thread = agent.get_new_thread(service_thread_id=conversation_id)
# Create a session with the existing ID
session = agent.create_session(service_session_id=conversation_id)
query = "What's the weather like in Seattle?"
print(f"User: {query}")
result = await agent.run(query, thread=thread)
result = await agent.run(query, session=session)
print(f"Agent: {result.text}\n")
query = "What was my last question?"
print(f"User: {query}")
result = await agent.run(query, thread=thread)
result = await agent.run(query, session=session)
print(f"Agent: {result.text}\n")
async def main() -> None:
await example_with_conversation_id()
await example_with_thread()
await example_with_session()
if __name__ == "__main__":
@@ -20,7 +20,7 @@ settings rather than relying on environment variable defaults.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -3,7 +3,7 @@
import asyncio
from typing import Any
from agent_framework import AgentResponse, AgentThread, Message, SupportsAgentRun
from agent_framework import AgentResponse, AgentSession, Message, SupportsAgentRun
from agent_framework.azure import AzureAIClient, AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
@@ -14,8 +14,8 @@ This sample demonstrates integrating hosted Model Context Protocol (MCP) tools w
"""
async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun") -> AgentResponse:
"""When we don't have a thread, we need to ensure we return with the input, approval request and approval."""
async def handle_approvals_without_session(query: str, agent: "SupportsAgentRun") -> AgentResponse:
"""When we don't have a session, we need to ensure we return with the input, approval request and approval."""
result = await agent.run(query, store=False)
while len(result.user_input_requests) > 0:
@@ -35,10 +35,10 @@ async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun")
return result
async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", thread: "AgentThread") -> AgentResponse:
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
async def handle_approvals_with_session(query: str, agent: "SupportsAgentRun", session: "AgentSession") -> AgentResponse:
"""Here we let the session deal with the previous responses, and we just rerun with the approval."""
result = await agent.run(query, thread=thread)
result = await agent.run(query, session=session)
while len(result.user_input_requests) > 0:
new_input: list[Any] = []
for user_input_needed in result.user_input_requests:
@@ -53,7 +53,7 @@ async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", th
contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")],
)
)
result = await agent.run(new_input, thread=thread)
result = await agent.run(new_input, session=session)
return result
@@ -82,13 +82,13 @@ async def run_hosted_mcp_without_approval() -> None:
query = "How to create an Azure storage account using az cli?"
print(f"User: {query}")
result = await handle_approvals_without_thread(query, agent)
result = await handle_approvals_without_session(query, agent)
print(f"{agent.name}: {result}\n")
async def run_hosted_mcp_with_approval_and_thread() -> None:
"""Example showing MCP Tools with approvals using a thread."""
print("=== MCP with approvals and with thread ===")
async def run_hosted_mcp_with_approval_and_session() -> None:
"""Example showing MCP Tools with approvals using a session."""
print("=== MCP with approvals and with session ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -111,10 +111,10 @@ async def run_hosted_mcp_with_approval_and_thread() -> None:
tools=[mcp_tool],
)
thread = agent.get_new_thread()
session = agent.create_session()
query = "Please summarize the Azure REST API specifications Readme"
print(f"User: {query}")
result = await handle_approvals_with_thread(query, agent, thread)
result = await handle_approvals_with_session(query, agent, session)
print(f"{agent.name}: {result}\n")
@@ -122,7 +122,7 @@ async def main() -> None:
print("=== Azure AI Agent with Hosted MCP Tools Example ===\n")
await run_hosted_mcp_without_approval()
await run_hosted_mcp_with_approval_and_thread()
await run_hosted_mcp_with_approval_and_session()
if __name__ == "__main__":
@@ -10,17 +10,17 @@ from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent with Thread Management Example
Azure AI Agent with Session Management Example
This sample demonstrates thread management with Azure AI Agent, showing
persistent conversation capabilities using service-managed threads as well as storing messages in-memory.
This sample demonstrates session management with Azure AI Agent, showing
persistent conversation capabilities using service-managed sessions as well as storing messages in-memory.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production
# See:
# samples/02-agents/tools/function_tool_with_approval.py
# samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -30,9 +30,9 @@ def get_weather(
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation."""
print("=== Automatic Thread Creation Example ===")
async def example_with_automatic_session_creation() -> None:
"""Example showing automatic session creation."""
print("=== Automatic Session Creation Example ===")
async with (
AzureCliCredential() as credential,
@@ -44,26 +44,26 @@ async def example_with_automatic_thread_creation() -> None:
tools=get_weather,
)
# First conversation - no thread provided, will be created automatically
# First conversation - no session provided, will be created automatically
query1 = "What's the weather like in Seattle?"
print(f"User: {query1}")
result1 = await agent.run(query1)
print(f"Agent: {result1.text}")
# Second conversation - still no thread provided, will create another new thread
# Second conversation - still no session provided, will create another new session
query2 = "What was the last city I asked about?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2)
print(f"Agent: {result2.text}")
print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n")
print("Note: Each call creates a separate session, so the agent doesn't remember previous context.\n")
async def example_with_thread_persistence_in_memory() -> None:
async def example_with_session_persistence_in_memory() -> None:
"""
Example showing thread persistence across multiple conversations.
Example showing session persistence across multiple conversations.
In this example, messages are stored in-memory.
"""
print("=== Thread Persistence Example (In-Memory) ===")
print("=== Session Persistence Example (In-Memory) ===")
async with (
AzureCliCredential() as credential,
@@ -75,38 +75,38 @@ async def example_with_thread_persistence_in_memory() -> None:
tools=get_weather,
)
# Create a new thread that will be reused
thread = agent.get_new_thread()
# Create a new session that will be reused
session = agent.create_session()
# First conversation
first_query = "What's the weather like in Tokyo?"
print(f"User: {first_query}")
first_result = await agent.run(first_query, thread=thread, options={"store": False})
first_result = await agent.run(first_query, session=session, options={"store": False})
print(f"Agent: {first_result.text}")
# Second conversation using the same thread - maintains context
# Second conversation using the same session - maintains context
second_query = "How about London?"
print(f"\nUser: {second_query}")
second_result = await agent.run(second_query, thread=thread, options={"store": False})
second_result = await agent.run(second_query, session=session, options={"store": False})
print(f"Agent: {second_result.text}")
# Third conversation - agent should remember both previous cities
third_query = "Which of the cities I asked about has better weather?"
print(f"\nUser: {third_query}")
third_result = await agent.run(third_query, thread=thread, options={"store": False})
third_result = await agent.run(third_query, session=session, options={"store": False})
print(f"Agent: {third_result.text}")
print("Note: The agent remembers context from previous messages in the same thread.\n")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_thread_id() -> None:
async def example_with_existing_session_id() -> None:
"""
Example showing how to work with an existing thread ID from the service.
Example showing how to work with an existing session ID from the service.
In this example, messages are stored on the server.
"""
print("=== Existing Thread ID Example ===")
print("=== Existing Session ID Example ===")
# First, create a conversation and capture the thread ID
existing_thread_id = None
# First, create a conversation and capture the session ID
existing_session_id = None
async with (
AzureCliCredential() as credential,
@@ -118,20 +118,20 @@ async def example_with_existing_thread_id() -> None:
tools=get_weather,
)
# Start a conversation and get the thread ID
thread = agent.get_new_thread()
# Start a conversation and get the session ID
session = agent.create_session()
first_query = "What's the weather in Paris?"
print(f"User: {first_query}")
first_result = await agent.run(first_query, thread=thread)
first_result = await agent.run(first_query, session=session)
print(f"Agent: {first_result.text}")
# The thread ID is set after the first response
existing_thread_id = thread.service_thread_id
print(f"Thread ID: {existing_thread_id}")
# The session ID is set after the first response
existing_session_id = session.service_session_id
print(f"Session ID: {existing_session_id}")
if existing_thread_id:
print("\n--- Continuing with the same thread ID in a new agent instance ---")
if existing_session_id:
print("\n--- Continuing with the same session ID in a new agent instance ---")
# Create a new agent instance from the same provider
second_agent = await provider.create_agent(
@@ -140,22 +140,22 @@ async def example_with_existing_thread_id() -> None:
tools=get_weather,
)
# Create a thread with the existing ID
thread = second_agent.get_new_thread(service_thread_id=existing_thread_id)
# Create a session with the existing ID
session = second_agent.create_session(service_session_id=existing_session_id)
second_query = "What was the last city I asked about?"
print(f"User: {second_query}")
second_result = await second_agent.run(second_query, thread=thread)
second_result = await second_agent.run(second_query, session=session)
print(f"Agent: {second_result.text}")
print("Note: The agent continues the conversation from the previous thread by using thread ID.\n")
print("Note: The agent continues the conversation from the previous session by using session ID.\n")
async def main() -> None:
print("=== Azure AI Agent Thread Management Examples ===\n")
print("=== Azure AI Agent Session Management Examples ===\n")
await example_with_automatic_thread_creation()
await example_with_thread_persistence_in_memory()
await example_with_existing_thread_id()
await example_with_automatic_session_creation()
await example_with_session_persistence_in_memory()
await example_with_existing_session_id()
if __name__ == "__main__":
@@ -38,7 +38,7 @@ async with (
| [`azure_ai_with_code_interpreter_file_generation.py`](azure_ai_with_code_interpreter_file_generation.py) | Shows how to retrieve file IDs from code interpreter generated files using both streaming and non-streaming approaches. |
| [`azure_ai_with_code_interpreter.py`](azure_ai_with_code_interpreter.py) | Shows how to use `AzureAIAgentClient.get_code_interpreter_tool()` with Azure AI agents to write and execute Python code. Includes helper methods for accessing code interpreter data from response chunks. |
| [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with an existing SDK Agent object using `provider.as_agent()`. This wraps the agent without making HTTP calls. |
| [`azure_ai_with_existing_thread.py`](azure_ai_with_existing_thread.py) | Shows how to work with a pre-existing thread by providing the thread ID. Demonstrates proper cleanup of manually created threads. |
| [`azure_ai_with_existing_session.py`](azure_ai_with_existing_session.py) | Shows how to work with a pre-existing session by providing the session ID. Demonstrates proper cleanup of manually created sessions. |
| [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured provider settings, including project endpoint and model deployment name. |
| [`azure_ai_with_azure_ai_search.py`](azure_ai_with_azure_ai_search.py) | Demonstrates how to use Azure AI Search with Azure AI agents. Shows how to create an agent with search tools using the SDK directly and wrap it with `provider.get_agent()`. |
| [`azure_ai_with_file_search.py`](azure_ai_with_file_search.py) | Demonstrates how to use `AzureAIAgentClient.get_file_search_tool()` with Azure AI agents to search through uploaded documents. Shows file upload, vector store creation, and querying document content. |
@@ -46,9 +46,9 @@ async with (
| [`azure_ai_with_hosted_mcp.py`](azure_ai_with_hosted_mcp.py) | Shows how to use `AzureAIAgentClient.get_mcp_tool()` with hosted Model Context Protocol (MCP) servers for enhanced functionality and tool integration. Demonstrates remote MCP server connections and tool discovery. |
| [`azure_ai_with_local_mcp.py`](azure_ai_with_local_mcp.py) | Shows how to integrate Azure AI agents with local Model Context Protocol (MCP) servers for enhanced functionality and tool integration. Demonstrates both agent-level and run-level tool configuration. |
| [`azure_ai_with_multiple_tools.py`](azure_ai_with_multiple_tools.py) | Demonstrates how to use multiple tools together with Azure AI agents, including web search, MCP servers, and function tools using client static methods. Shows coordinated multi-tool interactions and approval workflows. |
| [`azure_ai_with_openapi_tools.py`](azure_ai_with_openapi_tools.py) | Demonstrates how to use OpenAPI tools with Azure AI agents to integrate external REST APIs. Shows OpenAPI specification loading, anonymous authentication, thread context management, and coordinated multi-API conversations. |
| [`azure_ai_with_openapi_tools.py`](azure_ai_with_openapi_tools.py) | Demonstrates how to use OpenAPI tools with Azure AI agents to integrate external REST APIs. Shows OpenAPI specification loading, anonymous authentication, session context management, and coordinated multi-API conversations. |
| [`azure_ai_with_response_format.py`](azure_ai_with_response_format.py) | Demonstrates how to use structured outputs with Azure AI agents using Pydantic models. |
| [`azure_ai_with_thread.py`](azure_ai_with_thread.py) | Demonstrates thread management with Azure AI agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
| [`azure_ai_with_session.py`](azure_ai_with_session.py) | Demonstrates session management with Azure AI agents, including automatic session creation for stateless conversations and explicit session management for maintaining conversation context across multiple interactions. |
## Environment Variables
@@ -17,7 +17,7 @@ lifecycle management. Shows both streaming and non-streaming responses with func
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -21,7 +21,7 @@ This sample demonstrates the methods available on the AzureAIAgentsProvider clas
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -12,16 +12,16 @@ from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent with Existing Thread Example
Azure AI Agent with Existing Session Example
This sample demonstrates working with pre-existing conversation threads
by providing thread IDs for thread reuse patterns.
This sample demonstrates working with pre-existing conversation sessions
by providing session IDs for session reuse patterns.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -32,7 +32,7 @@ def get_weather(
async def main() -> None:
print("=== Azure AI Agent with Existing Thread ===")
print("=== Azure AI Agent with Existing Session ===")
# Create the client and provider
async with (
@@ -40,7 +40,7 @@ async def main() -> None:
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
AzureAIAgentsProvider(agents_client=agents_client) as provider,
):
# Create a thread that will persist
# Create a session that will persist
created_thread = await agents_client.threads.create()
try:
@@ -51,12 +51,11 @@ async def main() -> None:
tools=get_weather,
)
thread = agent.get_new_thread(service_thread_id=created_thread.id)
assert thread.is_initialized
result = await agent.run("What's the weather like in Tokyo?", thread=thread)
session = agent.get_session(service_session_id=created_thread.id)
result = await agent.run("What's the weather like in Tokyo?", session=session)
print(f"Result: {result}\n")
finally:
# Clean up the thread manually
# Clean up the session manually
await agents_client.threads.delete(created_thread.id)
@@ -20,7 +20,7 @@ settings rather than relying on environment variable defaults.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ showing both agent-level and query-level tool configuration patterns.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -3,7 +3,7 @@
import asyncio
from typing import Any
from agent_framework import AgentResponse, AgentThread, SupportsAgentRun
from agent_framework import AgentResponse, AgentSession, SupportsAgentRun
from agent_framework.azure import AzureAIAgentClient, AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
@@ -15,11 +15,11 @@ servers, including user approval workflows for function call security.
"""
async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", thread: "AgentThread") -> AgentResponse:
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
async def handle_approvals_with_session(query: str, agent: "SupportsAgentRun", session: "AgentSession") -> AgentResponse:
"""Here we let the session deal with the previous responses, and we just rerun with the approval."""
from agent_framework import Message
result = await agent.run(query, thread=thread, store=True)
result = await agent.run(query, session=session, store=True)
while len(result.user_input_requests) > 0:
new_input: list[Any] = []
for user_input_needed in result.user_input_requests:
@@ -34,7 +34,7 @@ async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", th
contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")],
)
)
result = await agent.run(new_input, thread=thread, store=True)
result = await agent.run(new_input, session=session, store=True)
return result
@@ -58,17 +58,17 @@ async def main() -> None:
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=[mcp_tool],
)
thread = agent.get_new_thread()
session = agent.create_session()
# First query
query1 = "How to create an Azure storage account using az cli?"
print(f"User: {query1}")
result1 = await handle_approvals_with_thread(query1, agent, thread)
result1 = await handle_approvals_with_session(query1, agent, session)
print(f"{agent.name}: {result1}\n")
print("\n=======================================\n")
# Second query
query2 = "What is Microsoft Agent Framework?"
print(f"User: {query2}")
result2 = await handle_approvals_with_thread(query2, agent, thread)
result2 = await handle_approvals_with_session(query2, agent, session)
print(f"{agent.name}: {result2}\n")
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
from typing import Any
from agent_framework import (
AgentThread,
AgentSession,
SupportsAgentRun,
tool,
)
@@ -35,7 +35,7 @@ To set up Bing Grounding:
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_time() -> str:
"""Get the current UTC time."""
@@ -43,11 +43,11 @@ def get_time() -> str:
return f"The current UTC time is {current_time.strftime('%Y-%m-%d %H:%M:%S')}."
async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", thread: "AgentThread"):
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
async def handle_approvals_with_session(query: str, agent: "SupportsAgentRun", session: "AgentSession"):
"""Here we let the session deal with the previous responses, and we just rerun with the approval."""
from agent_framework import Message
result = await agent.run(query, thread=thread, store=True)
result = await agent.run(query, session=session, store=True)
while len(result.user_input_requests) > 0:
new_input: list[Any] = []
for user_input_needed in result.user_input_requests:
@@ -62,7 +62,7 @@ async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", th
contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")],
)
)
result = await agent.run(new_input, thread=thread, store=True)
result = await agent.run(new_input, session=session, store=True)
return result
@@ -91,17 +91,17 @@ async def main() -> None:
get_time,
],
)
thread = agent.get_new_thread()
session = agent.create_session()
# First query
query1 = "How to create an Azure storage account using az cli and what time is it?"
print(f"User: {query1}")
result1 = await handle_approvals_with_thread(query1, agent, thread)
result1 = await handle_approvals_with_session(query1, agent, session)
print(f"{agent.name}: {result1}\n")
print("\n=======================================\n")
# Second query
query2 = "What is Microsoft Agent Framework and use a web search to see what is Reddit saying about it?"
print(f"User: {query2}")
result2 = await handle_approvals_with_thread(query2, agent, thread)
result2 = await handle_approvals_with_session(query2, agent, session)
print(f"{agent.name}: {result2}\n")
@@ -76,16 +76,16 @@ async def main() -> None:
tools=[*openapi_countries.definitions, *openapi_weather.definitions],
)
# 5. Simulate conversation with the agent maintaining thread context
# 5. Simulate conversation with the agent maintaining session context
print("=== Azure AI Agent with OpenAPI Tools ===\n")
# Create a thread to maintain conversation context across multiple runs
thread = agent.get_new_thread()
# Create a session to maintain conversation context across multiple runs
session = agent.create_session()
for user_input in USER_INPUTS:
print(f"User: {user_input}")
# Pass the thread to maintain context across multiple agent.run() calls
response = await agent.run(user_input, thread=thread)
# Pass the session to maintain context across multiple agent.run() calls
response = await agent.run(user_input, session=session)
print(f"Agent: {response.text}\n")
@@ -4,22 +4,22 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import AgentThread, tool
from agent_framework import AgentSession, tool
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
from pydantic import Field
"""
Azure AI Agent with Thread Management Example
Azure AI Agent with Session Management Example
This sample demonstrates thread management with Azure AI Agents, comparing
automatic thread creation with explicit thread management for persistent context.
This sample demonstrates session management with Azure AI Agents, comparing
automatic session creation with explicit session management for persistent context.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -29,9 +29,9 @@ def get_weather(
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation (service-managed thread)."""
print("=== Automatic Thread Creation Example ===")
async def example_with_automatic_session_creation() -> None:
"""Example showing automatic session creation (service-managed session)."""
print("=== Automatic Session Creation Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -45,24 +45,24 @@ async def example_with_automatic_thread_creation() -> None:
tools=get_weather,
)
# First conversation - no thread provided, will be created automatically
# First conversation - no session provided, will be created automatically
first_query = "What's the weather like in Seattle?"
print(f"User: {first_query}")
first_result = await agent.run(first_query)
print(f"Agent: {first_result.text}")
# Second conversation - still no thread provided, will create another new thread
# Second conversation - still no session provided, will create another new session
second_query = "What was the last city I asked about?"
print(f"\nUser: {second_query}")
second_result = await agent.run(second_query)
print(f"Agent: {second_result.text}")
print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n")
print("Note: Each call creates a separate session, so the agent doesn't remember previous context.\n")
async def example_with_thread_persistence() -> None:
"""Example showing thread persistence across multiple conversations."""
print("=== Thread Persistence Example ===")
print("Using the same thread across multiple conversations to maintain context.\n")
async def example_with_session_persistence() -> None:
"""Example showing session persistence across multiple conversations."""
print("=== Session Persistence Example ===")
print("Using the same session across multiple conversations to maintain context.\n")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -76,36 +76,36 @@ async def example_with_thread_persistence() -> None:
tools=get_weather,
)
# Create a new thread that will be reused
thread = agent.get_new_thread()
# Create a new session that will be reused
session = agent.create_session()
# First conversation
first_query = "What's the weather like in Tokyo?"
print(f"User: {first_query}")
first_result = await agent.run(first_query, thread=thread)
first_result = await agent.run(first_query, session=session)
print(f"Agent: {first_result.text}")
# Second conversation using the same thread - maintains context
# Second conversation using the same session - maintains context
second_query = "How about London?"
print(f"\nUser: {second_query}")
second_result = await agent.run(second_query, thread=thread)
second_result = await agent.run(second_query, session=session)
print(f"Agent: {second_result.text}")
# Third conversation - agent should remember both previous cities
third_query = "Which of the cities I asked about has better weather?"
print(f"\nUser: {third_query}")
third_result = await agent.run(third_query, thread=thread)
third_result = await agent.run(third_query, session=session)
print(f"Agent: {third_result.text}")
print("Note: The agent remembers context from previous messages in the same thread.\n")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_thread_id() -> None:
"""Example showing how to work with an existing thread ID from the service."""
print("=== Existing Thread ID Example ===")
print("Using a specific thread ID to continue an existing conversation.\n")
async def example_with_existing_session_id() -> None:
"""Example showing how to work with an existing session ID from the service."""
print("=== Existing Session ID Example ===")
print("Using a specific session ID to continue an existing conversation.\n")
# First, create a conversation and capture the thread ID
existing_thread_id = None
# First, create a conversation and capture the session ID
existing_session_id = None
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -119,21 +119,21 @@ async def example_with_existing_thread_id() -> None:
tools=get_weather,
)
# Start a conversation and get the thread ID
thread = agent.get_new_thread()
# Start a conversation and get the session ID
session = agent.create_session()
first_query = "What's the weather in Paris?"
print(f"User: {first_query}")
first_result = await agent.run(first_query, thread=thread)
first_result = await agent.run(first_query, session=session)
print(f"Agent: {first_result.text}")
# The thread ID is set after the first response
existing_thread_id = thread.service_thread_id
print(f"Thread ID: {existing_thread_id}")
# The session ID is set after the first response
existing_session_id = session.service_session_id
print(f"Session ID: {existing_session_id}")
if existing_thread_id:
print("\n--- Continuing with the same thread ID in a new agent instance ---")
if existing_session_id:
print("\n--- Continuing with the same session ID in a new agent instance ---")
# Create a new provider and agent but use the existing thread ID
# Create a new provider and agent but use the existing session ID
async with (
AzureCliCredential() as credential,
AzureAIAgentsProvider(credential=credential) as provider,
@@ -144,22 +144,22 @@ async def example_with_existing_thread_id() -> None:
tools=get_weather,
)
# Create a thread with the existing ID
thread = AgentThread(service_thread_id=existing_thread_id)
# Create a session with the existing ID
session = AgentSession(service_session_id=existing_session_id)
second_query = "What was the last city I asked about?"
print(f"User: {second_query}")
second_result = await agent.run(second_query, thread=thread)
second_result = await agent.run(second_query, session=session)
print(f"Agent: {second_result.text}")
print("Note: The agent continues the conversation from the previous thread.\n")
print("Note: The agent continues the conversation from the previous session.\n")
async def main() -> None:
print("=== Azure AI Chat Client Agent Thread Management Examples ===\n")
print("=== Azure AI Chat Client Agent Session Management Examples ===\n")
await example_with_automatic_thread_creation()
await example_with_thread_persistence()
await example_with_existing_thread_id()
await example_with_automatic_session_creation()
await example_with_session_persistence()
await example_with_existing_session_id()
if __name__ == "__main__":
@@ -11,11 +11,11 @@ This folder contains examples demonstrating different ways to create and use age
| [`azure_assistants_with_existing_assistant.py`](azure_assistants_with_existing_assistant.py) | Shows how to work with a pre-existing assistant by providing the assistant ID to the Azure Assistants client. Demonstrates proper cleanup of manually created assistants. |
| [`azure_assistants_with_explicit_settings.py`](azure_assistants_with_explicit_settings.py) | Shows how to initialize an agent with a specific assistants client, configuring settings explicitly including endpoint and deployment name. |
| [`azure_assistants_with_function_tools.py`](azure_assistants_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
| [`azure_assistants_with_thread.py`](azure_assistants_with_thread.py) | Demonstrates thread management with Azure agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
| [`azure_assistants_with_session.py`](azure_assistants_with_session.py) | Demonstrates session management with Azure agents, including automatic session creation for stateless conversations and explicit session management for maintaining conversation context across multiple interactions. |
| [`azure_chat_client_basic.py`](azure_chat_client_basic.py) | The simplest way to create an agent using `Agent` with `AzureOpenAIChatClient`. Shows both streaming and non-streaming responses for chat-based interactions with Azure OpenAI models. |
| [`azure_chat_client_with_explicit_settings.py`](azure_chat_client_with_explicit_settings.py) | Shows how to initialize an agent with a specific chat client, configuring settings explicitly including endpoint and deployment name. |
| [`azure_chat_client_with_function_tools.py`](azure_chat_client_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
| [`azure_chat_client_with_thread.py`](azure_chat_client_with_thread.py) | Demonstrates thread management with Azure agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
| [`azure_chat_client_with_session.py`](azure_chat_client_with_session.py) | Demonstrates session management with Azure agents, including automatic session creation for stateless conversations and explicit session management for maintaining conversation context across multiple interactions. |
| [`azure_responses_client_basic.py`](azure_responses_client_basic.py) | The simplest way to create an agent using `Agent` with `AzureOpenAIResponsesClient`. Shows both streaming and non-streaming responses for structured response generation with Azure OpenAI models. |
| [`azure_responses_client_code_interpreter_files.py`](azure_responses_client_code_interpreter_files.py) | Demonstrates using `AzureOpenAIResponsesClient.get_code_interpreter_tool()` with file uploads for data analysis. Shows how to create, upload, and analyze CSV files using Python code execution with Azure OpenAI Responses. |
| [`azure_responses_client_image_analysis.py`](azure_responses_client_image_analysis.py) | Shows how to use Azure OpenAI Responses for image analysis and vision tasks. Demonstrates multi-modal messages combining text and image content using remote URLs. |
@@ -26,7 +26,7 @@ This folder contains examples demonstrating different ways to create and use age
| [`azure_responses_client_with_function_tools.py`](azure_responses_client_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
| [`azure_responses_client_with_hosted_mcp.py`](azure_responses_client_with_hosted_mcp.py) | Shows how to integrate Azure OpenAI Responses Client with hosted Model Context Protocol (MCP) servers using `AzureOpenAIResponsesClient.get_mcp_tool()` for extended functionality. |
| [`azure_responses_client_with_local_mcp.py`](azure_responses_client_with_local_mcp.py) | Shows how to integrate Azure OpenAI Responses Client with local Model Context Protocol (MCP) servers using MCPStreamableHTTPTool for extended functionality. |
| [`azure_responses_client_with_thread.py`](azure_responses_client_with_thread.py) | Demonstrates thread management with Azure agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
| [`azure_responses_client_with_session.py`](azure_responses_client_with_session.py) | Demonstrates session management with Azure agents, including automatic session creation for stateless conversations and explicit session management for maintaining conversation context across multiple interactions. |
## Environment Variables
@@ -17,7 +17,7 @@ assistant lifecycle management, showing both streaming and non-streaming respons
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -21,7 +21,7 @@ using existing assistant IDs rather than creating new ones.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ settings rather than relying on environment variable defaults.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ showing both agent-level and query-level tool configuration patterns.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -4,22 +4,22 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import Agent, AgentThread, tool
from agent_framework import Agent, AgentSession, tool
from agent_framework.azure import AzureOpenAIAssistantsClient
from azure.identity import AzureCliCredential
from pydantic import Field
"""
Azure OpenAI Assistants with Thread Management Example
Azure OpenAI Assistants with Session Management Example
This sample demonstrates thread management with Azure OpenAI Assistants, comparing
automatic thread creation with explicit thread management for persistent context.
This sample demonstrates session management with Azure OpenAI Assistants, comparing
automatic session creation with explicit session management for persistent context.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -29,9 +29,9 @@ def get_weather(
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation (service-managed thread)."""
print("=== Automatic Thread Creation Example ===")
async def example_with_automatic_session_creation() -> None:
"""Example showing automatic session creation (service-managed session)."""
print("=== Automatic Session Creation Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -40,24 +40,24 @@ async def example_with_automatic_thread_creation() -> None:
instructions="You are a helpful weather agent.",
tools=get_weather,
) as agent:
# First conversation - no thread provided, will be created automatically
# First conversation - no session provided, will be created automatically
query1 = "What's the weather like in Seattle?"
print(f"User: {query1}")
result1 = await agent.run(query1)
print(f"Agent: {result1.text}")
# Second conversation - still no thread provided, will create another new thread
# Second conversation - still no session provided, will create another new session
query2 = "What was the last city I asked about?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2)
print(f"Agent: {result2.text}")
print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n")
print("Note: Each call creates a separate session, so the agent doesn't remember previous context.\n")
async def example_with_thread_persistence() -> None:
"""Example showing thread persistence across multiple conversations."""
print("=== Thread Persistence Example ===")
print("Using the same thread across multiple conversations to maintain context.\n")
async def example_with_session_persistence() -> None:
"""Example showing session persistence across multiple conversations."""
print("=== Session Persistence Example ===")
print("Using the same session across multiple conversations to maintain context.\n")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -66,36 +66,36 @@ async def example_with_thread_persistence() -> None:
instructions="You are a helpful weather agent.",
tools=get_weather,
) as agent:
# Create a new thread that will be reused
thread = agent.get_new_thread()
# Create a new session that will be reused
session = agent.create_session()
# First conversation
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1.text}")
# Second conversation using the same thread - maintains context
# Second conversation using the same session - maintains context
query2 = "How about London?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2, thread=thread)
result2 = await agent.run(query2, session=session)
print(f"Agent: {result2.text}")
# Third conversation - agent should remember both previous cities
query3 = "Which of the cities I asked about has better weather?"
print(f"\nUser: {query3}")
result3 = await agent.run(query3, thread=thread)
result3 = await agent.run(query3, session=session)
print(f"Agent: {result3.text}")
print("Note: The agent remembers context from previous messages in the same thread.\n")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_thread_id() -> None:
"""Example showing how to work with an existing thread ID from the service."""
print("=== Existing Thread ID Example ===")
print("Using a specific thread ID to continue an existing conversation.\n")
async def example_with_existing_session_id() -> None:
"""Example showing how to work with an existing session ID from the service."""
print("=== Existing Session ID Example ===")
print("Using a specific session ID to continue an existing conversation.\n")
# First, create a conversation and capture the thread ID
existing_thread_id = None
# First, create a conversation and capture the session ID
existing_session_id = None
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -104,42 +104,42 @@ async def example_with_existing_thread_id() -> None:
instructions="You are a helpful weather agent.",
tools=get_weather,
) as agent:
# Start a conversation and get the thread ID
thread = agent.get_new_thread()
# Start a conversation and get the session ID
session = agent.create_session()
query1 = "What's the weather in Paris?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1.text}")
# The thread ID is set after the first response
existing_thread_id = thread.service_thread_id
print(f"Thread ID: {existing_thread_id}")
# The session ID is set after the first response
existing_session_id = session.service_session_id
print(f"Session ID: {existing_session_id}")
if existing_thread_id:
print("\n--- Continuing with the same thread ID in a new agent instance ---")
if existing_session_id:
print("\n--- Continuing with the same session ID in a new agent instance ---")
# Create a new agent instance but use the existing thread ID
# Create a new agent instance but use the existing session ID
async with Agent(
client=AzureOpenAIAssistantsClient(thread_id=existing_thread_id, credential=AzureCliCredential()),
client=AzureOpenAIAssistantsClient(thread_id=existing_session_id, credential=AzureCliCredential()),
instructions="You are a helpful weather agent.",
tools=get_weather,
) as agent:
# Create a thread with the existing ID
thread = AgentThread(service_thread_id=existing_thread_id)
# Create a session with the existing ID
session = AgentSession(service_session_id=existing_session_id)
query2 = "What was the last city I asked about?"
print(f"User: {query2}")
result2 = await agent.run(query2, thread=thread)
result2 = await agent.run(query2, session=session)
print(f"Agent: {result2.text}")
print("Note: The agent continues the conversation from the previous thread.\n")
print("Note: The agent continues the conversation from the previous session.\n")
async def main() -> None:
print("=== Azure OpenAI Assistants Chat Client Agent Thread Management Examples ===\n")
print("=== Azure OpenAI Assistants Chat Client Agent Session Management Examples ===\n")
await example_with_automatic_thread_creation()
await example_with_thread_persistence()
await example_with_existing_thread_id()
await example_with_automatic_session_creation()
await example_with_session_persistence()
await example_with_existing_session_id()
if __name__ == "__main__":
@@ -19,7 +19,7 @@ interactions, showing both streaming and non-streaming responses.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ settings rather than relying on environment variable defaults.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ showing both agent-level and query-level tool configuration patterns.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -4,22 +4,22 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import Agent, AgentThread, ChatMessageStore, tool
from agent_framework import Agent, AgentSession, tool
from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import AzureCliCredential
from pydantic import Field
"""
Azure OpenAI Chat Client with Thread Management Example
Azure OpenAI Chat Client with Session Management Example
This sample demonstrates thread management with Azure OpenAI Chat Client, comparing
automatic thread creation with explicit thread management for persistent context.
This sample demonstrates session management with Azure OpenAI Chat Client, comparing
automatic session creation with explicit session management for persistent context.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -29,9 +29,9 @@ def get_weather(
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation (service-managed thread)."""
print("=== Automatic Thread Creation Example ===")
async def example_with_automatic_session_creation() -> None:
"""Example showing automatic session creation (service-managed session)."""
print("=== Automatic Session Creation Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -41,24 +41,24 @@ async def example_with_automatic_thread_creation() -> None:
tools=get_weather,
)
# First conversation - no thread provided, will be created automatically
# First conversation - no session provided, will be created automatically
query1 = "What's the weather like in Seattle?"
print(f"User: {query1}")
result1 = await agent.run(query1)
print(f"Agent: {result1.text}")
# Second conversation - still no thread provided, will create another new thread
# Second conversation - still no session provided, will create another new session
query2 = "What was the last city I asked about?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2)
print(f"Agent: {result2.text}")
print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n")
print("Note: Each call creates a separate session, so the agent doesn't remember previous context.\n")
async def example_with_thread_persistence() -> None:
"""Example showing thread persistence across multiple conversations."""
print("=== Thread Persistence Example ===")
print("Using the same thread across multiple conversations to maintain context.\n")
async def example_with_session_persistence() -> None:
"""Example showing session persistence across multiple conversations."""
print("=== Session Persistence Example ===")
print("Using the same session across multiple conversations to maintain context.\n")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -68,32 +68,32 @@ async def example_with_thread_persistence() -> None:
tools=get_weather,
)
# Create a new thread that will be reused
thread = agent.get_new_thread()
# Create a new session that will be reused
session = agent.create_session()
# First conversation
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1.text}")
# Second conversation using the same thread - maintains context
# Second conversation using the same session - maintains context
query2 = "How about London?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2, thread=thread)
result2 = await agent.run(query2, session=session)
print(f"Agent: {result2.text}")
# Third conversation - agent should remember both previous cities
query3 = "Which of the cities I asked about has better weather?"
print(f"\nUser: {query3}")
result3 = await agent.run(query3, thread=thread)
result3 = await agent.run(query3, session=session)
print(f"Agent: {result3.text}")
print("Note: The agent remembers context from previous messages in the same thread.\n")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_thread_messages() -> None:
"""Example showing how to work with existing thread messages for Azure."""
print("=== Existing Thread Messages Example ===")
async def example_with_existing_session_messages() -> None:
"""Example showing how to work with existing session messages for Azure."""
print("=== Existing Session Messages Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -104,53 +104,53 @@ async def example_with_existing_thread_messages() -> None:
)
# Start a conversation and build up message history
thread = agent.get_new_thread()
session = agent.create_session()
query1 = "What's the weather in Paris?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1.text}")
# The thread now contains the conversation history in memory
if thread.message_store:
messages = await thread.message_store.list_messages()
print(f"Thread contains {len(messages or [])} messages")
# The session now contains the conversation history in state
memory_state = session.state.get("memory", {})
messages = memory_state.get("messages", [])
if messages:
print(f"Session contains {len(messages)} messages")
print("\n--- Continuing with the same thread in a new agent instance ---")
print("\n--- Continuing with the same session in a new agent instance ---")
# Create a new agent instance but use the existing thread with its message history
# Create a new agent instance but use the existing session with its message history
new_agent = Agent(
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
# Use the same thread object which contains the conversation history
# Use the same session object which contains the conversation history
query2 = "What was the last city I asked about?"
print(f"User: {query2}")
result2 = await new_agent.run(query2, thread=thread)
result2 = await new_agent.run(query2, session=session)
print(f"Agent: {result2.text}")
print("Note: The agent continues the conversation using the local message history.\n")
print("\n--- Alternative: Creating a new thread from existing messages ---")
print("\n--- Alternative: Creating a new session from existing messages ---")
# You can also create a new thread from existing messages
messages = await thread.message_store.list_messages() if thread.message_store else []
new_thread = AgentThread(message_store=ChatMessageStore(messages))
# You can also create a new session from existing messages
new_session = AgentSession()
query3 = "How does the Paris weather compare to London?"
print(f"User: {query3}")
result3 = await new_agent.run(query3, thread=new_thread)
result3 = await new_agent.run(query3, session=new_session)
print(f"Agent: {result3.text}")
print("Note: This creates a new thread with the same conversation history.\n")
print("Note: This creates a new session with the same conversation history.\n")
async def main() -> None:
print("=== Azure Chat Client Agent Thread Management Examples ===\n")
print("=== Azure Chat Client Agent Session Management Examples ===\n")
await example_with_automatic_thread_creation()
await example_with_thread_persistence()
await example_with_existing_thread_messages()
await example_with_automatic_session_creation()
await example_with_session_persistence()
await example_with_existing_session_messages()
if __name__ == "__main__":
@@ -19,7 +19,7 @@ response generation, showing both streaming and non-streaming responses.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ settings rather than relying on environment variable defaults.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -28,7 +28,7 @@ This requires:
load_dotenv() # Load environment variables from .env file if present
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -20,7 +20,7 @@ showing both agent-level and query-level tool configuration patterns.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -15,11 +15,11 @@ Azure OpenAI Responses Client, including user approval workflows for function ca
"""
if TYPE_CHECKING:
from agent_framework import AgentThread, SupportsAgentRun
from agent_framework import AgentSession, SupportsAgentRun
async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun"):
"""When we don't have a thread, we need to ensure we return with the input, approval request and approval."""
async def handle_approvals_without_session(query: str, agent: "SupportsAgentRun"):
"""When we don't have a session, we need to ensure we return with the input, approval request and approval."""
from agent_framework import Message
result = await agent.run(query)
@@ -43,11 +43,11 @@ async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun")
return result
async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", thread: "AgentThread"):
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
async def handle_approvals_with_session(query: str, agent: "SupportsAgentRun", session: "AgentSession"):
"""Here we let the session deal with the previous responses, and we just rerun with the approval."""
from agent_framework import Message
result = await agent.run(query, thread=thread, store=True)
result = await agent.run(query, session=session, store=True)
while len(result.user_input_requests) > 0:
new_input: list[Any] = []
for user_input_needed in result.user_input_requests:
@@ -62,12 +62,12 @@ async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", th
contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")],
)
)
result = await agent.run(new_input, thread=thread, store=True)
result = await agent.run(new_input, session=session, store=True)
return result
async def handle_approvals_with_thread_streaming(query: str, agent: "SupportsAgentRun", thread: "AgentThread"):
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
async def handle_approvals_with_session_streaming(query: str, agent: "SupportsAgentRun", session: "AgentSession"):
"""Here we let the session deal with the previous responses, and we just rerun with the approval."""
from agent_framework import Message
new_input: list[Message] = []
@@ -75,7 +75,7 @@ async def handle_approvals_with_thread_streaming(query: str, agent: "SupportsAge
while new_input_added:
new_input_added = False
new_input.append(Message(role="user", text=query))
async for update in agent.run(new_input, thread=thread, options={"store": True}, stream=True):
async for update in agent.run(new_input, session=session, options={"store": True}, stream=True):
if update.user_input_requests:
for user_input_needed in update.user_input_requests:
print(
@@ -94,9 +94,9 @@ async def handle_approvals_with_thread_streaming(query: str, agent: "SupportsAge
yield update
async def run_hosted_mcp_without_thread_and_specific_approval() -> None:
"""Example showing Mcp Tools with approvals without using a thread."""
print("=== Mcp with approvals and without thread ===")
async def run_hosted_mcp_without_session_and_specific_approval() -> None:
"""Example showing Mcp Tools with approvals without using a session."""
print("=== Mcp with approvals and without session ===")
credential = AzureCliCredential()
client = AzureOpenAIResponsesClient(credential=credential)
@@ -120,13 +120,13 @@ async def run_hosted_mcp_without_thread_and_specific_approval() -> None:
# First query
query1 = "How to create an Azure storage account using az cli?"
print(f"User: {query1}")
result1 = await handle_approvals_without_thread(query1, agent)
result1 = await handle_approvals_without_session(query1, agent)
print(f"{agent.name}: {result1}\n")
print("\n=======================================\n")
# Second query
query2 = "What is Microsoft Agent Framework?"
print(f"User: {query2}")
result2 = await handle_approvals_without_thread(query2, agent)
result2 = await handle_approvals_without_session(query2, agent)
print(f"{agent.name}: {result2}\n")
@@ -157,19 +157,19 @@ async def run_hosted_mcp_without_approval() -> None:
# First query
query1 = "How to create an Azure storage account using az cli?"
print(f"User: {query1}")
result1 = await handle_approvals_without_thread(query1, agent)
result1 = await handle_approvals_without_session(query1, agent)
print(f"{agent.name}: {result1}\n")
print("\n=======================================\n")
# Second query
query2 = "What is Microsoft Agent Framework?"
print(f"User: {query2}")
result2 = await handle_approvals_without_thread(query2, agent)
result2 = await handle_approvals_without_session(query2, agent)
print(f"{agent.name}: {result2}\n")
async def run_hosted_mcp_with_thread() -> None:
"""Example showing Mcp Tools with approvals using a thread."""
print("=== Mcp with approvals and with thread ===")
async def run_hosted_mcp_with_session() -> None:
"""Example showing Mcp Tools with approvals using a session."""
print("=== Mcp with approvals and with session ===")
credential = AzureCliCredential()
client = AzureOpenAIResponsesClient(credential=credential)
@@ -190,22 +190,22 @@ async def run_hosted_mcp_with_thread() -> None:
tools=[mcp_tool],
) as agent:
# First query
thread = agent.get_new_thread()
session = agent.create_session()
query1 = "How to create an Azure storage account using az cli?"
print(f"User: {query1}")
result1 = await handle_approvals_with_thread(query1, agent, thread)
result1 = await handle_approvals_with_session(query1, agent, session)
print(f"{agent.name}: {result1}\n")
print("\n=======================================\n")
# Second query
query2 = "What is Microsoft Agent Framework?"
print(f"User: {query2}")
result2 = await handle_approvals_with_thread(query2, agent, thread)
result2 = await handle_approvals_with_session(query2, agent, session)
print(f"{agent.name}: {result2}\n")
async def run_hosted_mcp_with_thread_streaming() -> None:
"""Example showing Mcp Tools with approvals using a thread."""
print("=== Mcp with approvals and with thread ===")
async def run_hosted_mcp_with_session_streaming() -> None:
"""Example showing Mcp Tools with approvals using a session."""
print("=== Mcp with approvals and with session ===")
credential = AzureCliCredential()
client = AzureOpenAIResponsesClient(credential=credential)
@@ -226,11 +226,11 @@ async def run_hosted_mcp_with_thread_streaming() -> None:
tools=[mcp_tool],
) as agent:
# First query
thread = agent.get_new_thread()
session = agent.create_session()
query1 = "How to create an Azure storage account using az cli?"
print(f"User: {query1}")
print(f"{agent.name}: ", end="")
async for update in handle_approvals_with_thread_streaming(query1, agent, thread):
async for update in handle_approvals_with_session_streaming(query1, agent, session):
print(update, end="")
print("\n")
print("\n=======================================\n")
@@ -238,7 +238,7 @@ async def run_hosted_mcp_with_thread_streaming() -> None:
query2 = "What is Microsoft Agent Framework?"
print(f"User: {query2}")
print(f"{agent.name}: ", end="")
async for update in handle_approvals_with_thread_streaming(query2, agent, thread):
async for update in handle_approvals_with_session_streaming(query2, agent, session):
print(update, end="")
print("\n")
@@ -247,9 +247,9 @@ async def main() -> None:
print("=== OpenAI Responses Client Agent with Hosted Mcp Tools Examples ===\n")
await run_hosted_mcp_without_approval()
await run_hosted_mcp_without_thread_and_specific_approval()
await run_hosted_mcp_with_thread()
await run_hosted_mcp_with_thread_streaming()
await run_hosted_mcp_without_session_and_specific_approval()
await run_hosted_mcp_with_session()
await run_hosted_mcp_with_session_streaming()
if __name__ == "__main__":
@@ -4,22 +4,22 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import Agent, AgentThread, tool
from agent_framework import Agent, AgentSession, tool
from agent_framework.azure import AzureOpenAIResponsesClient
from azure.identity import AzureCliCredential
from pydantic import Field
"""
Azure OpenAI Responses Client with Thread Management Example
Azure OpenAI Responses Client with Session Management Example
This sample demonstrates thread management with Azure OpenAI Responses Client, comparing
automatic thread creation with explicit thread management for persistent context.
This sample demonstrates session management with Azure OpenAI Responses Client, comparing
automatic session creation with explicit session management for persistent context.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -29,9 +29,9 @@ def get_weather(
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation."""
print("=== Automatic Thread Creation Example ===")
async def example_with_automatic_session_creation() -> None:
"""Example showing automatic session creation."""
print("=== Automatic Session Creation Example ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -41,26 +41,26 @@ async def example_with_automatic_thread_creation() -> None:
tools=get_weather,
)
# First conversation - no thread provided, will be created automatically
# First conversation - no session provided, will be created automatically
query1 = "What's the weather like in Seattle?"
print(f"User: {query1}")
result1 = await agent.run(query1)
print(f"Agent: {result1.text}")
# Second conversation - still no thread provided, will create another new thread
# Second conversation - still no session provided, will create another new session
query2 = "What was the last city I asked about?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2)
print(f"Agent: {result2.text}")
print("Note: Each call creates a separate thread, so the agent doesn't remember previous context.\n")
print("Note: Each call creates a separate session, so the agent doesn't remember previous context.\n")
async def example_with_thread_persistence_in_memory() -> None:
async def example_with_session_persistence_in_memory() -> None:
"""
Example showing thread persistence across multiple conversations.
Example showing session persistence across multiple conversations.
In this example, messages are stored in-memory.
"""
print("=== Thread Persistence Example (In-Memory) ===")
print("=== Session Persistence Example (In-Memory) ===")
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -70,38 +70,38 @@ async def example_with_thread_persistence_in_memory() -> None:
tools=get_weather,
)
# Create a new thread that will be reused
thread = agent.get_new_thread()
# Create a new session that will be reused
session = agent.create_session()
# First conversation
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1.text}")
# Second conversation using the same thread - maintains context
# Second conversation using the same session - maintains context
query2 = "How about London?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2, thread=thread)
result2 = await agent.run(query2, session=session)
print(f"Agent: {result2.text}")
# Third conversation - agent should remember both previous cities
query3 = "Which of the cities I asked about has better weather?"
print(f"\nUser: {query3}")
result3 = await agent.run(query3, thread=thread)
result3 = await agent.run(query3, session=session)
print(f"Agent: {result3.text}")
print("Note: The agent remembers context from previous messages in the same thread.\n")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_thread_id() -> None:
async def example_with_existing_session_id() -> None:
"""
Example showing how to work with an existing thread ID from the service.
Example showing how to work with an existing session ID from the service.
In this example, messages are stored on the server using Azure OpenAI conversation state.
"""
print("=== Existing Thread ID Example ===")
print("=== Existing Session ID Example ===")
# First, create a conversation and capture the thread ID
existing_thread_id = None
# First, create a conversation and capture the session ID
existing_session_id = None
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
# authentication option.
@@ -111,21 +111,21 @@ async def example_with_existing_thread_id() -> None:
tools=get_weather,
)
# Start a conversation and get the thread ID
thread = agent.get_new_thread()
# Start a conversation and get the session ID
session = agent.create_session()
query1 = "What's the weather in Paris?"
print(f"User: {query1}")
# Enable Azure OpenAI conversation state by setting `store` parameter to True
result1 = await agent.run(query1, thread=thread, store=True)
result1 = await agent.run(query1, session=session, store=True)
print(f"Agent: {result1.text}")
# The thread ID is set after the first response
existing_thread_id = thread.service_thread_id
print(f"Thread ID: {existing_thread_id}")
# The session ID is set after the first response
existing_session_id = session.service_session_id
print(f"Session ID: {existing_session_id}")
if existing_thread_id:
print("\n--- Continuing with the same thread ID in a new agent instance ---")
if existing_session_id:
print("\n--- Continuing with the same session ID in a new agent instance ---")
agent = Agent(
client=AzureOpenAIResponsesClient(credential=AzureCliCredential()),
@@ -133,22 +133,22 @@ async def example_with_existing_thread_id() -> None:
tools=get_weather,
)
# Create a thread with the existing ID
thread = AgentThread(service_thread_id=existing_thread_id)
# Create a session with the existing ID
session = AgentSession(service_session_id=existing_session_id)
query2 = "What was the last city I asked about?"
print(f"User: {query2}")
result2 = await agent.run(query2, thread=thread, store=True)
result2 = await agent.run(query2, session=session, store=True)
print(f"Agent: {result2.text}")
print("Note: The agent continues the conversation from the previous thread by using thread ID.\n")
print("Note: The agent continues the conversation from the previous session by using session ID.\n")
async def main() -> None:
print("=== Azure OpenAI Response Client Agent Thread Management Examples ===\n")
print("=== Azure OpenAI Response Client Agent Session Management Examples ===\n")
await example_with_automatic_thread_creation()
await example_with_thread_persistence_in_memory()
await example_with_existing_thread_id()
await example_with_automatic_session_creation()
await example_with_session_persistence_in_memory()
await example_with_existing_session_id()
if __name__ == "__main__":
@@ -6,7 +6,7 @@ This folder contains examples demonstrating how to implement custom agents and c
| File | Description |
|------|-------------|
| [`custom_agent.py`](custom_agent.py) | Shows how to create custom agents by extending the `BaseAgent` class. Demonstrates the `EchoAgent` implementation with both streaming and non-streaming responses, proper thread management, and message history handling. |
| [`custom_agent.py`](custom_agent.py) | Shows how to create custom agents by extending the `BaseAgent` class. Demonstrates the `EchoAgent` implementation with both streaming and non-streaming responses, proper session management, and message history handling. |
| [`custom_chat_client.py`](../../chat_client/custom_chat_client.py) | Demonstrates how to create custom chat clients by extending the `BaseChatClient` class. Shows a `EchoingChatClient` implementation and how to integrate it with `Agent` using the `as_agent()` method. |
## Key Takeaways
@@ -15,7 +15,7 @@ This folder contains examples demonstrating how to implement custom agents and c
- Custom agents give you complete control over the agent's behavior
- You must implement both `run()` for both the `stream=True` and `stream=False` cases
- Use `self._normalize_messages()` to handle different input message formats
- Use `self._notify_thread_of_new_messages()` to properly manage conversation history
- Store messages in `session.state` to properly manage conversation history
### Custom Chat Clients
- Custom chat clients allow you to integrate any backend service or create new LLM providers
@@ -7,7 +7,7 @@ from typing import Any
from agent_framework import (
AgentResponse,
AgentResponseUpdate,
AgentThread,
AgentSession,
BaseAgent,
Content,
Message,
@@ -60,7 +60,7 @@ class EchoAgent(BaseAgent):
messages: str | Message | list[str] | list[Message] | None = None,
*,
stream: bool = False,
thread: AgentThread | None = None,
session: AgentSession | None = None,
**kwargs: Any,
) -> "AsyncIterable[AgentResponseUpdate] | asyncio.Future[AgentResponse]":
"""Execute the agent and return a response.
@@ -68,7 +68,7 @@ class EchoAgent(BaseAgent):
Args:
messages: The message(s) to process.
stream: If True, return an async iterable of updates. If False, return an awaitable response.
thread: The conversation thread (optional).
session: The conversation session (optional).
**kwargs: Additional keyword arguments.
Returns:
@@ -76,14 +76,14 @@ class EchoAgent(BaseAgent):
When stream=True: An async iterable of AgentResponseUpdate objects.
"""
if stream:
return self._run_stream(messages=messages, thread=thread, **kwargs)
return self._run(messages=messages, thread=thread, **kwargs)
return self._run_stream(messages=messages, session=session, **kwargs)
return self._run(messages=messages, session=session, **kwargs)
async def _run(
self,
messages: str | Message | list[str] | list[Message] | None = None,
*,
thread: AgentThread | None = None,
session: AgentSession | None = None,
**kwargs: Any,
) -> AgentResponse:
"""Non-streaming implementation."""
@@ -105,9 +105,11 @@ class EchoAgent(BaseAgent):
response_message = Message(role=Role.ASSISTANT, contents=[Content.from_text(text=echo_text)])
# Notify the thread of new messages if provided
if thread is not None:
await self._notify_thread_of_new_messages(thread, normalized_messages, response_message)
# Store messages in session state if provided
if session is not None:
stored = session.state.setdefault("memory", {}).setdefault("messages", [])
stored.extend(normalized_messages)
stored.append(response_message)
return AgentResponse(messages=[response_message])
@@ -115,7 +117,7 @@ class EchoAgent(BaseAgent):
self,
messages: str | Message | list[str] | list[Message] | None = None,
*,
thread: AgentThread | None = None,
session: AgentSession | None = None,
**kwargs: Any,
) -> AsyncIterable[AgentResponseUpdate]:
"""Streaming implementation."""
@@ -146,10 +148,12 @@ class EchoAgent(BaseAgent):
# Small delay to simulate streaming
await asyncio.sleep(0.1)
# Notify the thread of the complete response if provided
if thread is not None:
# Store messages in session state if provided
if session is not None:
complete_response = Message(role=Role.ASSISTANT, contents=[Content.from_text(text=response_text)])
await self._notify_thread_of_new_messages(thread, normalized_messages, complete_response)
stored = session.state.setdefault("memory", {}).setdefault("messages", [])
stored.extend(normalized_messages)
stored.append(complete_response)
async def main() -> None:
@@ -180,26 +184,27 @@ async def main() -> None:
print(chunk.text, end="", flush=True)
print()
# Example with threads
print("\n--- Using Custom Agent with Thread ---")
thread = echo_agent.get_new_thread()
# Example with sessions
print("\n--- Using Custom Agent with Session ---")
session = echo_agent.create_session()
# First message
result1 = await echo_agent.run("First message", thread=thread)
result1 = await echo_agent.run("First message", session=session)
print("User: First message")
print(f"Agent: {result1.messages[0].text}")
# Second message in same thread
result2 = await echo_agent.run("Second message", thread=thread)
result2 = await echo_agent.run("Second message", session=session)
print("User: Second message")
print(f"Agent: {result2.messages[0].text}")
# Check conversation history
if thread.message_store:
messages = await thread.message_store.list_messages()
print(f"\nThread contains {len(messages)} messages in history")
memory_state = session.state.get("memory", {})
messages = memory_state.get("messages", [])
if messages:
print(f"\nSession contains {len(messages)} messages in history")
else:
print("\nThread has no message store configured")
print("\nSession has no messages stored")
if __name__ == "__main__":
@@ -29,7 +29,7 @@ The following environment variables can be configured:
| File | Description |
|------|-------------|
| [`github_copilot_basic.py`](github_copilot_basic.py) | The simplest way to create an agent using `GitHubCopilotAgent`. Demonstrates both streaming and non-streaming responses with function tools. |
| [`github_copilot_with_session.py`](github_copilot_with_session.py) | Shows session management with automatic creation, persistence via thread objects, and resuming sessions by ID. |
| [`github_copilot_with_session.py`](github_copilot_with_session.py) | Shows session management with automatic creation, persistence via session objects, and resuming sessions by ID. |
| [`github_copilot_with_shell.py`](github_copilot_with_shell.py) | Shows how to enable shell command execution permissions. Demonstrates running system commands like listing files and getting system information. |
| [`github_copilot_with_file_operations.py`](github_copilot_with_file_operations.py) | Shows how to enable file read and write permissions. Demonstrates reading file contents and creating new files. |
| [`github_copilot_with_url.py`](github_copilot_with_url.py) | Shows how to enable URL fetching permissions. Demonstrates fetching and processing web content. |
@@ -22,7 +22,7 @@ from agent_framework.github import GitHubCopilotAgent
from pydantic import Field
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -17,7 +17,7 @@ from agent_framework.github import GitHubCopilotAgent
from pydantic import Field
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
@@ -61,31 +61,31 @@ async def example_with_session_persistence() -> None:
)
async with agent:
# Create a thread to maintain conversation context
thread = agent.get_new_thread()
# Create a session to maintain conversation context
session = agent.create_session()
# First query
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
result1 = await agent.run(query1, session=session)
print(f"Agent: {result1}")
# Second query - using same thread maintains context
query2 = "How about London?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2, thread=thread)
result2 = await agent.run(query2, session=session)
print(f"Agent: {result2}")
# Third query - agent should remember both previous cities
query3 = "Which of the cities I asked about has better weather?"
print(f"\nUser: {query3}")
result3 = await agent.run(query3, thread=thread)
result3 = await agent.run(query3, session=session)
print(f"Agent: {result3}")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_session_id() -> None:
"""Resume session in new agent instance using service_thread_id."""
"""Resume session in new agent instance using service_session_id."""
print("=== Existing Session ID Example ===")
existing_session_id = None
@@ -97,15 +97,15 @@ async def example_with_existing_session_id() -> None:
)
async with agent1:
thread = agent1.get_new_thread()
session = agent1.create_session()
query1 = "What's the weather in Paris?"
print(f"User: {query1}")
result1 = await agent1.run(query1, thread=thread)
result1 = await agent1.run(query1, session=session)
print(f"Agent: {result1}")
# Capture the session ID for later use
existing_session_id = thread.service_thread_id
existing_session_id = session.service_session_id
print(f"Session ID: {existing_session_id}")
if existing_session_id:
@@ -118,12 +118,12 @@ async def example_with_existing_session_id() -> None:
)
async with agent2:
# Create thread with existing session ID
thread = agent2.get_new_thread(service_thread_id=existing_session_id)
# Create session with existing session ID
session = agent2.create_session(service_session_id=existing_session_id)
query2 = "What was the last city I asked about?"
print(f"User: {query2}")
result2 = await agent2.run(query2, thread=thread)
result2 = await agent2.run(query2, session=session)
print(f"Agent: {result2}")
print("Note: The agent continues the conversation using the session ID.\n")
@@ -19,7 +19,7 @@ https://ollama.com/
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_time(location: str) -> str:
"""Get the current time."""
@@ -19,7 +19,7 @@ https://ollama.com/
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_time():
"""Get the current time."""
@@ -21,7 +21,7 @@ Environment Variables:
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_threads.py.
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, "The location to get the weather for."],

Some files were not shown because too many files have changed in this diff Show More