Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions python/samples/02-agents/context_providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ These samples demonstrate how to use context providers to enrich agent conversat
| File / Folder | Description |
|---------------|-------------|
| [`simple_context_provider.py`](simple_context_provider.py) | Implement a custom context provider by extending `ContextProvider` to extract and inject structured user information across turns. |
| [`todo_provider.py`](todo_provider.py) | Use the built-in `TodoProvider` to give an agent todo-list tools. A scripted walkthrough that plans multi-step work and prints the evolving todo list after each turn. |
| [`agent_mode_provider.py`](agent_mode_provider.py) | Use the built-in `AgentModeProvider` to track and switch an agent's operating mode at runtime. An interactive loop with a `/mode` slash command demonstrating the built-in `plan`/`execute` modes and custom modes. |
| [`cross_session_observer.py`](cross_session_observer.py) | Detect injected context messages whose origins differ from the current session, via the `Message.additional_properties["_attribution"]["origin_session_ids"]` field. Self-contained — no LLM credentials required. |
| [`azure_ai_foundry_memory.py`](azure_ai_foundry_memory.py) | Use `FoundryMemoryProvider` to add semantic memory — automatically retrieves, searches, and stores memories via Microsoft Foundry. |
| [`file_access_data_processing/`](file_access_data_processing/) | Use `FileAccessProvider` with `FileSystemAgentFileStore` to give an agent read/write/search access to a folder of CSV data files. See its own [README](file_access_data_processing/README.md). |
Expand All @@ -25,6 +27,18 @@ These samples demonstrate how to use context providers to enrich agent conversat
- `FOUNDRY_MODEL`: Model deployment name
- Azure CLI authentication (`az login`)

**For `todo_provider.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Microsoft Foundry project endpoint
- `FOUNDRY_MODEL`: Model deployment name
- Azure CLI authentication (`az login`)

**For `agent_mode_provider.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Microsoft Foundry project endpoint
- `FOUNDRY_MODEL`: Model deployment name
- Azure CLI authentication (`az login`)
- To try the custom `concise`/`detailed` modes instead of the built-in `plan`/`execute` modes, set the in-file `USE_CUSTOM_MODES` constant to `True`.
- This sample is interactive: it reads commands from the console in a loop (type `/exit` to quit).

**For `azure_ai_foundry_memory.py`:**
- `FOUNDRY_PROJECT_ENDPOINT`: Your Microsoft Foundry project endpoint
- `FOUNDRY_MODEL`: Chat/responses model deployment name
Expand Down
180 changes: 180 additions & 0 deletions python/samples/02-agents/context_providers/agent_mode_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
import os

from agent_framework import Agent, AgentModeProvider, get_agent_mode, set_agent_mode
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

"""
Agent Mode — Switch an agent's operating mode at runtime with AgentModeProvider

This sample shows how to use the ``AgentModeProvider``, a ``ContextProvider`` that tracks the
agent's current operating "mode" in the session state and exposes tools (``mode_get`` / ``mode_set``)
so the agent can query and switch modes as its work progresses. The mode is folded into the
instructions sent to the model on every turn, so different modes can drive different behavior.

The sample demonstrates two things:
1. The built-in default modes ("plan" and "execute") that ship with the provider.
2. How to customize the available modes via ``default_mode`` / ``mode_instructions``. Flip the
``USE_CUSTOM_MODES`` constant below to ``True`` to try a simple concise/detailed mode set.

It runs a simple interactive loop. In addition to chatting with the agent, you can switch the
agent's mode yourself using a slash command:
/mode — show the current mode
/mode <name> — switch to the named mode
/help — list the available commands and modes
/exit — quit

When you switch modes with /mode, the provider injects a notification on the next turn so the agent
clearly sees the change and adjusts its behavior accordingly.

Environment variables:
FOUNDRY_PROJECT_ENDPOINT — Microsoft Foundry project endpoint URL
FOUNDRY_MODEL — Model deployment name

Authentication:
Run ``az login`` before running this sample.
"""

# Flip to True to run the sample with the custom modes defined below instead of the provider's
# built-in "plan" / "execute" defaults.
USE_CUSTOM_MODES = False


def print_help(available_modes: tuple[str, ...]) -> None:
"""Print the available slash commands and modes."""
print("Commands:")
print(" /mode Show the current mode")
print(f" /mode <name> Switch mode ({' | '.join(available_modes)})")
print(" /help Show this help")
print(" /exit Quit")


async def main() -> None:
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AzureCliCredential(),
)

# <create_mode_provider>
if USE_CUSTOM_MODES:
# Customize the set of modes by supplying ``mode_instructions``. Each mode maps a name to a
# block of instructions describing how the agent should behave while operating in that mode.
# ``default_mode`` selects the mode new sessions start in (defaults to the first mode when
# omitted).
mode_provider = AgentModeProvider(
default_mode="concise",
mode_instructions={
"concise": (
"Answer in a single short sentence. Do not elaborate unless the user explicitly "
"asks for more detail."
),
"detailed": (
"Answer thoroughly. Explain your reasoning, provide examples, and cover relevant edge cases."
),
},
)
else:
# Use the provider's built-in modes: "plan" (interactive planning) and "execute" (autonomous
# execution). No options are required.
mode_provider = AgentModeProvider()
# </create_mode_provider>

available_modes = mode_provider.available_modes

# Create the agent and attach the mode provider as a context provider.
agent = Agent(
client=client,
name="ModeAwareAssistant",
instructions=(
"You are a helpful assistant. Follow the process and behavior required by your current operating mode."
),
context_providers=[mode_provider],
)

session = agent.create_session()

def current_mode() -> str:
"""Read the active mode from the session, validated against the provider's configured modes."""
return get_agent_mode(
session,
source_id=mode_provider.source_id,
default_mode=mode_provider.default_mode,
available_modes=available_modes,
)

print("Agent Mode sample. Type a message to chat, or use a slash command.")
print(f"Available modes: {', '.join(available_modes)}")
print(f"Current mode: {current_mode()}")
print_help(available_modes)
print()

while True:
user_input = input("> ").strip()

# Treat empty input or /exit as a request to quit.
if not user_input or user_input.lower() == "/exit":
break

if user_input.lower() == "/help":
print_help(available_modes)
continue

# Handle the /mode slash command: "/mode" shows the current mode, "/mode <name>" switches.
if user_input.lower() == "/mode" or user_input.lower().startswith("/mode "):
parts = user_input.split(maxsplit=1)
if len(parts) < 2:
print(f"Current mode: {current_mode()}")
continue

try:
# ``set_agent_mode`` records the switch so the provider injects a notification on the
# next turn. It raises ValueError when the requested mode is not configured.
new_mode = set_agent_mode(
session,
parts[1],
source_id=mode_provider.source_id,
available_modes=available_modes,
)
print(f'Switched to "{new_mode}" mode.')
except ValueError as ex:
print(ex)

continue

# Anything else is a message for the agent. The mode provider injects the current mode (and
# any pending mode-change notification) into the context for this turn.
print(await agent.run(user_input, session=session))


if __name__ == "__main__":
asyncio.run(main())


"""
Sample interaction (abridged; exact text varies by model):

Agent Mode sample. Type a message to chat, or use a slash command.
Available modes: plan, execute
Current mode: plan
Commands:
/mode Show the current mode
/mode <name> Switch mode (plan | execute)
/help Show this help
/exit Quit

> Help me plan a blog post about the ocean.
Sure — before we start writing, let's outline the sections and audience. ...
> /mode execute
Switched to "execute" mode.
> Go ahead and write it.
Working through the plan autonomously now. Here's the first draft ...
> /exit
"""
127 changes: 127 additions & 0 deletions python/samples/02-agents/context_providers/todo_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
import os

from agent_framework import Agent, AgentSession, TodoItem, TodoProvider
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

"""
Todo List — Track work items across turns with TodoProvider

This sample shows how to use the ``TodoProvider``, a ``ContextProvider`` that gives an agent a set
of tools for managing a todo list (``todos_add``, ``todos_complete``, ``todos_remove``,
``todos_get_remaining``, ``todos_get_all``) along with instructions on how to use them. The todo
list is stored in the session state and persists across turns, so the agent can plan multi-step
work, track progress, and adjust the list as the conversation evolves.

This is a scripted, non-interactive walkthrough: it sends a sequence of messages to the agent and,
after each turn, prints the agent's reply followed by the current todo list (read directly from the
provider's store). This lets you watch the todo state evolve as the agent adds, completes, and
removes items.

Environment variables:
FOUNDRY_PROJECT_ENDPOINT — Microsoft Foundry project endpoint URL
FOUNDRY_MODEL — Model deployment name

Authentication:
Run ``az login`` before running this sample.
"""


async def print_todo_list(todo_provider: TodoProvider, session: AgentSession) -> None:
"""Read the current todo list straight from the provider's store and print it."""
# The provider persists todos in its store keyed by ``source_id``; loading them lets the sample
# display the state without going through the model.
items: list[TodoItem] = await todo_provider.store.load_items(session, source_id=todo_provider.source_id)

print("--- Current todo list ---")
if not items:
print(" (empty)")
return

for item in items:
mark = "x" if item.is_complete else " "
line = f" [{mark}] {item.id}. {item.title}"
if item.description:
line += f" — {item.description}"
print(line)


async def main() -> None:
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AzureCliCredential(),
)

# <create_todo_provider>
# Create the TodoProvider and attach it to the agent as a context provider. The provider
# contributes the todo-management tools and instructions to every agent invocation.
todo_provider = TodoProvider()

agent = Agent(
client=client,
name="PlanningAssistant",
instructions="You are a helpful planning assistant. Use your todo list to plan and track multi-step work.",
context_providers=[todo_provider],
)
# </create_todo_provider>

# Reuse a single session so the todo state persists across turns.
session = agent.create_session()

# A scripted set of turns that exercises the provider end-to-end: the agent should add todos for
# a multi-step request, mark items complete as progress is reported, and adjust the list on a
# change of plan.
user_messages = [
"I'm organizing a small team offsite. Can you help me plan it? Break the work into a todo list.",
"I've booked the venue and sent out the invites. Please update the list.",
"Actually, let's skip catering and instead plan a group hike. Update the plan accordingly.",
]

for user_message in user_messages:
print(f"User: {user_message}")
print(f"Agent: {await agent.run(user_message, session=session)}")

# Print the current todo list so the evolving state is visible after each turn.
await print_todo_list(todo_provider, session)
print()


if __name__ == "__main__":
asyncio.run(main())


"""
Sample output (abridged; exact text varies by model):

User: I'm organizing a small team offsite. Can you help me plan it? Break the work into a todo list.
Agent: Great — I've broken this into a starter plan. Let me know if you'd like to adjust anything.
--- Current todo list ---
[ ] 1. Pick a date and confirm attendees
[ ] 2. Book a venue
[ ] 3. Arrange catering
[ ] 4. Send out invites

User: I've booked the venue and sent out the invites. Please update the list.
Agent: Nice work! I've marked the venue and invites as done.
--- Current todo list ---
[ ] 1. Pick a date and confirm attendees
[x] 2. Book a venue
[ ] 3. Arrange catering
[x] 4. Send out invites

User: Actually, let's skip catering and instead plan a group hike. Update the plan accordingly.
Agent: Done — I removed catering and added a group hike to the plan.
--- Current todo list ---
[ ] 1. Pick a date and confirm attendees
[x] 2. Book a venue
[x] 4. Send out invites
[ ] 5. Plan a group hike
"""
Loading