From 1bbd91e2696413c0659a7af104bcb41fc6a15086 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:50:42 +0000 Subject: [PATCH 01/12] Fix ChatHistory storage by service table (#826) --- agent-framework/user-guide/agents/agent-types/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent-framework/user-guide/agents/agent-types/index.md b/agent-framework/user-guide/agents/agent-types/index.md index 7f375a9e..37381e55 100644 --- a/agent-framework/user-guide/agents/agent-types/index.md +++ b/agent-framework/user-guide/agents/agent-types/index.md @@ -44,11 +44,11 @@ var agent = new ChatClientAgent(chatClient, instructions: "You are a helpful ass To make creating these agents even easier, Agent Framework provides helpers for many popular services. For more information, see the documentation for each service. -| Underlying inference service | Description | Service chat history storage support | Custom chat history storage support | -|------------------------------|-------------|--------------------------------------|-------------------------------------| +| Underlying inference service | Description | Service chat history storage supported | InMemory/Custom chat history storage supported | +|------------------------------|-------------|----------------------------------------|------------------------------------------------| |[Azure AI Foundry Agent](./azure-ai-foundry-agent.md)|An agent that uses the Azure AI Foundry Agents Service as its backend.|Yes|No| |[Azure AI Foundry Models ChatCompletion](./azure-ai-foundry-models-chat-completion-agent.md)|An agent that uses any of the models deployed in the Azure AI Foundry Service as its backend via ChatCompletion.|No|Yes| -|[Azure AI Foundry Models Responses](./azure-ai-foundry-models-responses-agent.md)|An agent that uses any of the models deployed in the Azure AI Foundry Service as its backend via Responses.|No|Yes| +|[Azure AI Foundry Models Responses](./azure-ai-foundry-models-responses-agent.md)|An agent that uses any of the models deployed in the Azure AI Foundry Service as its backend via Responses.|Yes|Yes| |[Azure OpenAI ChatCompletion](./azure-openai-chat-completion-agent.md)|An agent that uses the Azure OpenAI ChatCompletion service.|No|Yes| |[Azure OpenAI Responses](./azure-openai-responses-agent.md)|An agent that uses the Azure OpenAI Responses service.|Yes|Yes| |[OpenAI ChatCompletion](./openai-chat-completion-agent.md)|An agent that uses the OpenAI ChatCompletion service.|No|Yes| From f99085eb00191483c5fcab2fe412a60f93d55ee5 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:40:10 +0000 Subject: [PATCH 02/12] Rename ChatMessageStore to ChatHistoryProvider (#829) --- .../third-party-chat-history-storage.md | 36 ++++++++-------- .../user-guide/agents/agent-memory.md | 42 +++++++++---------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/agent-framework/tutorials/agents/third-party-chat-history-storage.md b/agent-framework/tutorials/agents/third-party-chat-history-storage.md index 525f7e9a..7a14e9aa 100644 --- a/agent-framework/tutorials/agents/third-party-chat-history-storage.md +++ b/agent-framework/tutorials/agents/third-party-chat-history-storage.md @@ -1,6 +1,6 @@ --- title: Storing Chat History in 3rd Party Storage -description: How to store agent chat history in external storage using a custom ChatMessageStore. +description: How to store agent chat history in external storage zone_pivot_groups: programming-languages author: westey-m ms.topic: tutorial @@ -13,7 +13,7 @@ ms.service: agent-framework ::: zone pivot="programming-language-csharp" -This tutorial shows how to store agent chat history in external storage by implementing a custom `ChatMessageStore` and using it with a `ChatClientAgent`. +This tutorial shows how to store agent chat history in external storage by implementing a custom `ChatHistoryProvider` and using it with a `ChatClientAgent`. By default, when using `ChatClientAgent`, chat history is stored either in memory in the `AgentThread` object or the underlying inference service, if the service supports it. @@ -39,9 +39,9 @@ In addition, you'll use the in-memory vector store to store chat messages. dotnet add package Microsoft.SemanticKernel.Connectors.InMemory --prerelease ``` -## Create a custom ChatMessage Store +## Create a custom ChatHistoryProvider -To create a custom `ChatMessageStore`, you need to implement the abstract `ChatMessageStore` class and provide implementations for the required methods. +To create a custom `ChatHistoryProvider`, you need to implement the abstract `ChatHistoryProvider` class and provide implementations for the required methods. ### Message storage and retrieval methods @@ -56,13 +56,13 @@ Any chat history reduction logic, such as summarization or trimming, should be d ### Serialization -`ChatMessageStore` instances are created and attached to an `AgentThread` when the thread is created, and when a thread is resumed from a serialized state. +`ChatHistoryProvider` instances are created and attached to an `AgentThread` when the thread is created, and when a thread is resumed from a serialized state. -While the actual messages making up the chat history are stored externally, the `ChatMessageStore` instance might need to store keys or other state to identify the chat history in the external store. +While the actual messages making up the chat history are stored externally, the `ChatHistoryProvider` instance might need to store keys or other state to identify the chat history in the external store. -To allow persisting threads, you need to implement the `Serialize` method of the `ChatMessageStore` class. This method should return a `JsonElement` containing the state needed to restore the store later. When deserializing, the agent framework will pass this serialized state to the ChatMessageStoreFactory, allowing you to use it to recreate the store. +To allow persisting threads, you need to implement the `Serialize` method of the `ChatHistoryProvider` class. This method should return a `JsonElement` containing the state needed to restore the provider later. When deserializing, the agent framework will pass this serialized state to the ChatHistoryProviderFactory, allowing you to use it to recreate the provider. -### Sample ChatMessageStore implementation +### Sample ChatHistoryProvider implementation The following sample implementation stores chat messages in a vector store. @@ -87,11 +87,11 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.VectorData; using Microsoft.SemanticKernel.Connectors.InMemory; -internal sealed class VectorChatMessageStore : ChatMessageStore +internal sealed class VectorChatHistoryProvider : ChatHistoryProvider { private readonly VectorStore _vectorStore; - public VectorChatMessageStore( + public VectorChatHistoryProvider( VectorStore vectorStore, JsonElement serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null) @@ -185,13 +185,13 @@ internal sealed class VectorChatMessageStore : ChatMessageStore } ``` -## Using the custom ChatMessageStore with a ChatClientAgent +## Using the custom ChatHistoryProvider with a ChatClientAgent -To use the custom `ChatMessageStore`, you need to provide a `ChatMessageStoreFactory` when creating the agent. This factory allows the agent to create a new instance of the desired `ChatMessageStore` for each thread. +To use the custom `ChatHistoryProvider`, you need to provide a `ChatHistoryProviderFactory` when creating the agent. This factory allows the agent to create a new instance of the desired `ChatHistoryProvider` for each thread. -When creating a `ChatClientAgent` it is possible to provide a `ChatClientAgentOptions` object that allows providing the `ChatMessageStoreFactory` in addition to all other agent options. +When creating a `ChatClientAgent` it is possible to provide a `ChatClientAgentOptions` object that allows providing the `ChatHistoryProviderFactory` in addition to all other agent options. -The factory is an async function that receives a context object and a cancellation token, and returns a `ValueTask`. +The factory is an async function that receives a context object and a cancellation token, and returns a `ValueTask`. ```csharp using Azure.AI.OpenAI; @@ -210,11 +210,11 @@ AIAgent agent = new AzureOpenAIClient( { Name = "Joker", ChatOptions = new() { Instructions = "You are good at telling jokes." }, - ChatMessageStoreFactory = (ctx, ct) => new ValueTask( - // Create a new chat message store for this agent that stores the messages in a vector store. - // Each thread must get its own copy of the VectorChatMessageStore, since the store + ChatHistoryProviderFactory = (ctx, ct) => new ValueTask( + // Create a new chat history provider for this agent that stores the messages in a vector store. + // Each thread must get its own copy of the VectorChatHistoryProvider, since the provider // also contains the id that the thread is stored under. - new VectorChatMessageStore( + new VectorChatHistoryProvider( vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions)) diff --git a/agent-framework/user-guide/agents/agent-memory.md b/agent-framework/user-guide/agents/agent-memory.md index f0e99887..9bee58ac 100644 --- a/agent-framework/user-guide/agents/agent-memory.md +++ b/agent-framework/user-guide/agents/agent-memory.md @@ -21,7 +21,7 @@ Various chat history storage options are supported by Agent Framework. The avail The two main supported scenarios are: -- **In-memory storage**: Agent is built on a service that doesn't support in-service storage of chat history (for example, OpenAI Chat Completion). By default, Agent Framework stores the full chat history in-memory in the `AgentThread` object, but developers can provide a custom `ChatMessageStore` implementation to store chat history in a third-party store if required. +- **In-memory storage**: Agent is built on a service that doesn't support in-service storage of chat history (for example, OpenAI Chat Completion). By default, Agent Framework stores the full chat history in-memory in the `AgentThread` object, but developers can provide a custom `ChatHistoryProvider` implementation to store chat history in a third-party store if required. - **In-service storage**: Agent is built on a service that requires in-service storage of chat history (for example, Azure AI Foundry Persistent Agents). Agent Framework stores the ID of the remote chat history in the `AgentThread` object, and no other chat history storage options are supported. ### In-memory chat history storage @@ -49,17 +49,17 @@ IList? messages = thread.GetService>(); #### Chat history reduction with in-memory storage -The built-in `InMemoryChatMessageStore` that's used by default when the underlying service does not support in-service storage, +The built-in `InMemoryChatHistoryProvider` that's used by default when the underlying service does not support in-service storage, can be configured with a reducer to manage the size of the chat history. This is useful to avoid exceeding the context size limits of the underlying service. -The `InMemoryChatMessageStore` can take an optional `Microsoft.Extensions.AI.IChatReducer` implementation to reduce the size of the chat history. +The `InMemoryChatHistoryProvider` can take an optional `Microsoft.Extensions.AI.IChatReducer` implementation to reduce the size of the chat history. It also allows you to configure the event during which the reducer is invoked, either after a message is added to the chat history or before the chat history is returned for the next invocation. -To configure the `InMemoryChatMessageStore` with a reducer, you can provide a factory to construct a new `InMemoryChatMessageStore` -for each new `AgentThread` and pass it a reducer of your choice. The `InMemoryChatMessageStore` can also be passed an optional trigger event -which can be set to either `InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded` or `InMemoryChatMessageStore.ChatReducerTriggerEvent.BeforeMessagesRetrieval`. +To configure the `InMemoryChatHistoryProvider` with a reducer, you can provide a factory to construct a new `InMemoryChatHistoryProvider` +for each new `AgentThread` and pass it a reducer of your choice. The `InMemoryChatHistoryProvider` can also be passed an optional trigger event +which can be set to either `InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded` or `InMemoryChatHistoryProvider.ChatReducerTriggerEvent.BeforeMessagesRetrieval`. The factory is an async function that receives a context object and a cancellation token. @@ -70,17 +70,17 @@ AIAgent agent = new OpenAIClient("") { Name = JokerName, ChatOptions = new() { Instructions = JokerInstructions }, - ChatMessageStoreFactory = (ctx, ct) => new ValueTask( - new InMemoryChatMessageStore( + ChatHistoryProviderFactory = (ctx, ct) => new ValueTask( + new InMemoryChatHistoryProvider( new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions, - InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded)) + InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded)) }); ``` > [!NOTE] -> This feature is only supported when using the `InMemoryChatMessageStore`. When a service has in-service chat history storage, it is up to the service itself to manage the size of the chat history. Similarly, when using 3rd party storage (see below), it is up to the 3rd party storage solution to manage the chat history size. If you provide a `ChatMessageStoreFactory` for a message store but you use a service with built-in chat history storage, the factory will not be used. +> This feature is only supported when using the `InMemoryChatHistoryProvider`. When a service has in-service chat history storage, it is up to the service itself to manage the size of the chat history. Similarly, when using 3rd party storage (see below), it is up to the 3rd party storage solution to manage the chat history size. If you provide a `ChatHistoryProviderFactory` for a chat history provider but you use a service with built-in chat history storage, the factory will not be used. ### Inference service chat history storage @@ -102,16 +102,16 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread) ### Third-party chat history storage -When using a service that does not support in-service storage of chat history, Agent Framework allows developers to replace the default in-memory storage of chat history with third-party chat history storage. The developer is required to provide a subclass of the base abstract `ChatMessageStore` class. +When using a service that does not support in-service storage of chat history, Agent Framework allows developers to replace the default in-memory storage of chat history with third-party chat history storage. The developer is required to provide a subclass of the base abstract `ChatHistoryProvider` class. -The `ChatMessageStore` class defines the interface for storing and retrieving chat messages. Developers must implement the `InvokedAsync` and `InvokingAsync` methods to add messages to the remote store as they are generated, and retrieve messages from the remote store before invoking the underlying service. +The `ChatHistoryProvider` class defines the interface for storing and retrieving chat messages. Developers must implement the `InvokedAsync` and `InvokingAsync` methods to add messages to the remote store as they are generated, and retrieve messages from the remote store before invoking the underlying service. -The agent will use all messages returned by `InvokingAsync` when processing a user query. It is up to the implementer of `ChatMessageStore` to ensure that the size of the chat history does not exceed the context window of the underlying service. +The agent will use all messages returned by `InvokingAsync` when processing a user query. It is up to the implementer of `ChatHistoryProvider` to ensure that the size of the chat history does not exceed the context window of the underlying service. -When implementing a custom `ChatMessageStore` which stores chat history in a remote store, the chat history for that thread should be stored under a key that is unique to that thread. The `ChatMessageStore` implementation should generate this key and keep it in its state. `ChatMessageStore` has a `Serialize` method that can be overridden to serialize its state when the thread is serialized. The `ChatMessageStore` should also provide a constructor that takes a as input to support deserialization of its state. +When implementing a custom `ChatHistoryProvider` which stores chat history in a remote store, the chat history for that thread should be stored under a key that is unique to that thread. The `ChatHistoryProvider` implementation should generate this key and keep it in its state. `ChatHistoryProvider` has a `Serialize` method that can be overridden to serialize its state when the thread is serialized. The `ChatHistoryProvider` should also provide a constructor that takes a as input to support deserialization of its state. -To supply a custom `ChatMessageStore` to a `ChatClientAgent`, you can use the `ChatMessageStoreFactory` option when creating the agent. -Here is an example showing how to pass the custom implementation of `ChatMessageStore` to a `ChatClientAgent` that is based on Azure OpenAI Chat Completion. +To supply a custom `ChatHistoryProvider` to a `ChatClientAgent`, you can use the `ChatHistoryProviderFactory` option when creating the agent. +Here is an example showing how to pass the custom implementation of `ChatHistoryProvider` to a `ChatClientAgent` that is based on Azure OpenAI Chat Completion. The factory is an async function that receives a context object and a cancellation token. @@ -124,11 +124,11 @@ AIAgent agent = new AzureOpenAIClient( { Name = JokerName, ChatOptions = new() { Instructions = JokerInstructions }, - ChatMessageStoreFactory = (ctx, ct) => new ValueTask( - // Create a new chat message store for this agent that stores the messages in a custom store. - // Each thread must get its own copy of the CustomMessageStore, since the store + ChatHistoryProviderFactory = (ctx, ct) => new ValueTask( + // Create a new chat history provider for this agent that stores the messages in a custom store. + // Each thread must get its own copy of the CustomChatHistoryProvider, since the provider // also contains the ID that the thread is stored under. - new CustomMessageStore( + new CustomChatHistoryProvider( vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions)) @@ -153,7 +153,7 @@ It is important to be able to persist an `AgentThread` object between agent invo Even if the chat history is stored in a remote store, the `AgentThread` object still contains an ID referencing the remote chat history. Losing the `AgentThread` state will therefore result in also losing the ID of the remote chat history. -The `AgentThread` as well as any objects attached to it, all therefore provide the `Serialize` method to serialize their state. The `AIAgent` also provides a `DeserializeThreadAsync` method that re-creates a thread from the serialized state. The `DeserializeThreadAsync` method re-creates the thread with the `ChatMessageStore` and `AIContextProvider` configured on the agent. +The `AgentThread` as well as any objects attached to it, all therefore provide the `Serialize` method to serialize their state. The `AIAgent` also provides a `DeserializeThreadAsync` method that re-creates a thread from the serialized state. The `DeserializeThreadAsync` method re-creates the thread with the `ChatHistoryProvider` and `AIContextProvider` configured on the agent. ```csharp // Serialize the thread state to a JsonElement, so it can be stored for later use. From d3a166c927958a46221ccc865c43bb14fb346427 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:01:03 -0800 Subject: [PATCH 03/12] Added page for GitHub Copilot SDK (#828) * Added page for GitHub Copilot SDK * Small fix --- .../user-guide/agents/agent-types/TOC.yml | 2 + .../agent-types/github-copilot-agent.md | 396 ++++++++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 agent-framework/user-guide/agents/agent-types/github-copilot-agent.md diff --git a/agent-framework/user-guide/agents/agent-types/TOC.yml b/agent-framework/user-guide/agents/agent-types/TOC.yml index 3a1f6a21..26348797 100644 --- a/agent-framework/user-guide/agents/agent-types/TOC.yml +++ b/agent-framework/user-guide/agents/agent-types/TOC.yml @@ -26,6 +26,8 @@ href: durable-agent/create-durable-agent.md - name: Durable Agent Features href: durable-agent/features.md +- name: GitHub Copilot Agents + href: github-copilot-agent.md - name: A2A Agents href: a2a-agent.md - name: Custom Agents diff --git a/agent-framework/user-guide/agents/agent-types/github-copilot-agent.md b/agent-framework/user-guide/agents/agent-types/github-copilot-agent.md new file mode 100644 index 00000000..c46a07d5 --- /dev/null +++ b/agent-framework/user-guide/agents/agent-types/github-copilot-agent.md @@ -0,0 +1,396 @@ +--- +title: GitHub Copilot Agents +description: Learn how to use Microsoft Agent Framework with the GitHub Copilot SDK. +zone_pivot_groups: programming-languages +author: dmytrostruk +ms.topic: tutorial +ms.author: dmytrostruk +ms.date: 01/26/2026 +ms.service: agent-framework +--- + +# GitHub Copilot Agents + +Microsoft Agent Framework supports creating agents that use the [GitHub Copilot SDK](https://github.com/github/copilot-sdk) as their backend. GitHub Copilot agents provide access to powerful coding-oriented AI capabilities, including shell command execution, file operations, URL fetching, and Model Context Protocol (MCP) server integration. + +> [!IMPORTANT] +> GitHub Copilot agents require the GitHub Copilot CLI to be installed and authenticated. For security, it is recommended to run agents with shell or file permissions in a containerized environment (Docker/Dev Container). + +::: zone pivot="programming-language-csharp" + +## Getting Started + +Add the required NuGet packages to your project. + +```dotnetcli +dotnet add package Microsoft.Agents.AI.GithubCopilot --prerelease +``` + +## Create a GitHub Copilot Agent + +As a first step, create a `CopilotClient` and start it. Then use the `AsAIAgent` extension method to create an agent. + +```csharp +using GitHub.Copilot.SDK; +using Microsoft.Agents.AI; + +await using CopilotClient copilotClient = new(); +await copilotClient.StartAsync(); + +AIAgent agent = copilotClient.AsAIAgent(); + +Console.WriteLine(await agent.RunAsync("What is Microsoft Agent Framework?")); +``` + +### With Tools and Instructions + +You can provide function tools and custom instructions when creating the agent: + +```csharp +using GitHub.Copilot.SDK; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; + +AIFunction weatherTool = AIFunctionFactory.Create((string location) => +{ + return $"The weather in {location} is sunny with a high of 25C."; +}, "GetWeather", "Get the weather for a given location."); + +await using CopilotClient copilotClient = new(); +await copilotClient.StartAsync(); + +AIAgent agent = copilotClient.AsAIAgent( + tools: [weatherTool], + instructions: "You are a helpful weather agent."); + +Console.WriteLine(await agent.RunAsync("What's the weather like in Seattle?")); +``` + +## Agent Features + +### Streaming Responses + +Get responses as they are generated: + +```csharp +await using CopilotClient copilotClient = new(); +await copilotClient.StartAsync(); + +AIAgent agent = copilotClient.AsAIAgent(); + +await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Tell me a short story.")) +{ + Console.Write(update); +} + +Console.WriteLine(); +``` + +### Session Management + +Maintain conversation context across multiple interactions using sessions: + +```csharp +await using CopilotClient copilotClient = new(); +await copilotClient.StartAsync(); + +await using GithubCopilotAgent agent = new( + copilotClient, + instructions: "You are a helpful assistant. Keep your answers short."); + +AgentSession session = await agent.GetNewSessionAsync(); + +// First turn +await agent.RunAsync("My name is Alice.", session); + +// Second turn - agent remembers the context +AgentResponse response = await agent.RunAsync("What is my name?", session); +Console.WriteLine(response); // Should mention "Alice" +``` + +### Permissions + +By default, the agent cannot execute shell commands, read/write files, or fetch URLs. To enable these capabilities, provide a permission handler via `SessionConfig`: + +```csharp +static Task PromptPermission( + PermissionRequest request, PermissionInvocation invocation) +{ + Console.WriteLine($"\n[Permission Request: {request.Kind}]"); + Console.Write("Approve? (y/n): "); + + string? input = Console.ReadLine()?.Trim().ToUpperInvariant(); + string kind = input is "Y" or "YES" ? "approved" : "denied-interactively-by-user"; + + return Task.FromResult(new PermissionRequestResult { Kind = kind }); +} + +await using CopilotClient copilotClient = new(); +await copilotClient.StartAsync(); + +SessionConfig sessionConfig = new() +{ + OnPermissionRequest = PromptPermission, +}; + +AIAgent agent = copilotClient.AsAIAgent(sessionConfig); + +Console.WriteLine(await agent.RunAsync("List all files in the current directory")); +``` + +### MCP Servers + +Connect to local (stdio) or remote (HTTP) MCP servers for extended capabilities: + +```csharp +await using CopilotClient copilotClient = new(); +await copilotClient.StartAsync(); + +SessionConfig sessionConfig = new() +{ + OnPermissionRequest = PromptPermission, + McpServers = new Dictionary + { + // Local stdio server + ["filesystem"] = new McpLocalServerConfig + { + Type = "stdio", + Command = "npx", + Args = ["-y", "@modelcontextprotocol/server-filesystem", "."], + Tools = ["*"], + }, + // Remote HTTP server + ["microsoft-learn"] = new McpRemoteServerConfig + { + Type = "http", + Url = "https://learn.microsoft.com/api/mcp", + Tools = ["*"], + }, + }, +}; + +AIAgent agent = copilotClient.AsAIAgent(sessionConfig); + +Console.WriteLine(await agent.RunAsync("Search Microsoft Learn for 'Azure Functions' and summarize the top result")); +``` + +## Using the Agent + +The agent is a standard `AIAgent` and supports all standard `AIAgent` operations. + +For more information on how to run and interact with agents, see the [Agent getting started tutorials](../../../tutorials/overview.md). + +::: zone-end +::: zone pivot="programming-language-python" + +## Prerequisites + +Install the Microsoft Agent Framework GitHub Copilot package. + +```bash +pip install agent-framework-github-copilot --pre +``` + +## Configuration + +The agent can be optionally configured using the following environment variables: + +| Variable | Description | +|----------|-------------| +| `GITHUB_COPILOT_CLI_PATH` | Path to the Copilot CLI executable | +| `GITHUB_COPILOT_MODEL` | Model to use (e.g., `gpt-5`, `claude-sonnet-4`) | +| `GITHUB_COPILOT_TIMEOUT` | Request timeout in seconds | +| `GITHUB_COPILOT_LOG_LEVEL` | CLI log level | + +## Getting Started + +Import the required classes from Agent Framework: + +```python +import asyncio +from agent_framework.github import GithubCopilotAgent, GithubCopilotOptions +``` + +## Create a GitHub Copilot Agent + +### Basic Agent Creation + +The simplest way to create a GitHub Copilot agent: + +```python +async def basic_example(): + agent = GithubCopilotAgent( + default_options={"instructions": "You are a helpful assistant."}, + ) + + async with agent: + result = await agent.run("What is Microsoft Agent Framework?") + print(result) +``` + +### With Explicit Configuration + +You can provide explicit configuration through `default_options`: + +```python +async def explicit_config_example(): + agent = GithubCopilotAgent( + default_options={ + "instructions": "You are a helpful assistant.", + "model": "gpt-5", + "timeout": 120, + }, + ) + + async with agent: + result = await agent.run("What can you do?") + print(result) +``` + +## Agent Features + +### Function Tools + +Equip your agent with custom functions: + +```python +from typing import Annotated +from pydantic import Field + +def get_weather( + location: Annotated[str, Field(description="The location to get the weather for.")], +) -> str: + """Get the weather for a given location.""" + return f"The weather in {location} is sunny with a high of 25C." + +async def tools_example(): + agent = GithubCopilotAgent( + default_options={"instructions": "You are a helpful weather agent."}, + tools=[get_weather], + ) + + async with agent: + result = await agent.run("What's the weather like in Seattle?") + print(result) +``` + +### Streaming Responses + +Get responses as they are generated for better user experience: + +```python +async def streaming_example(): + agent = GithubCopilotAgent( + default_options={"instructions": "You are a helpful assistant."}, + ) + + async with agent: + print("Agent: ", end="", flush=True) + async for chunk in agent.run_stream("Tell me a short story."): + if chunk.text: + print(chunk.text, end="", flush=True) + print() +``` + +### Thread Management + +Maintain conversation context across multiple interactions: + +```python +async def thread_example(): + agent = GithubCopilotAgent( + default_options={"instructions": "You are a helpful assistant."}, + ) + + async with agent: + thread = agent.get_new_thread() + + # First interaction + result1 = await agent.run("My name is Alice.", thread=thread) + print(f"Agent: {result1}") + + # Second interaction - agent remembers the context + result2 = await agent.run("What's my name?", thread=thread) + print(f"Agent: {result2}") # Should remember "Alice" +``` + +### Permissions + +By default, the agent cannot execute shell commands, read/write files, or fetch URLs. To enable these capabilities, provide a permission handler: + +```python +from copilot.types import PermissionRequest, PermissionRequestResult + +def prompt_permission( + request: PermissionRequest, context: dict[str, str] +) -> PermissionRequestResult: + kind = request.get("kind", "unknown") + print(f"\n[Permission Request: {kind}]") + + response = input("Approve? (y/n): ").strip().lower() + if response in ("y", "yes"): + return PermissionRequestResult(kind="approved") + return PermissionRequestResult(kind="denied-interactively-by-user") + +async def permissions_example(): + agent = GithubCopilotAgent( + default_options={ + "instructions": "You are a helpful assistant that can execute shell commands.", + "on_permission_request": prompt_permission, + }, + ) + + async with agent: + result = await agent.run("List the Python files in the current directory") + print(result) +``` + +### MCP Servers + +Connect to local (stdio) or remote (HTTP) MCP servers for extended capabilities: + +```python +from copilot.types import MCPServerConfig + +async def mcp_example(): + mcp_servers: dict[str, MCPServerConfig] = { + # Local stdio server + "filesystem": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "."], + "tools": ["*"], + }, + # Remote HTTP server + "microsoft-learn": { + "type": "http", + "url": "https://learn.microsoft.com/api/mcp", + "tools": ["*"], + }, + } + + agent = GithubCopilotAgent( + default_options={ + "instructions": "You are a helpful assistant with access to the filesystem and Microsoft Learn.", + "on_permission_request": prompt_permission, + "mcp_servers": mcp_servers, + }, + ) + + async with agent: + result = await agent.run("Search Microsoft Learn for 'Azure Functions' and summarize the top result") + print(result) +``` + +## Using the Agent + +The agent is a standard `BaseAgent` and supports all standard agent operations. + +For more information on how to run and interact with agents, see the [Agent getting started tutorials](../../../tutorials/overview.md). + +::: zone-end + +## Next steps + +> [!div class="nextstepaction"] +> [Custom Agents](./custom-agent.md) From 4b89a4759f4d3a201248fa3630575c6b440cfe02 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Tue, 27 Jan 2026 20:02:24 +0100 Subject: [PATCH 04/12] updated filtering section (#827) --- .../inmemory-connector.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md index 51086da7..db9be4df 100644 --- a/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md +++ b/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/inmemory-connector.md @@ -104,6 +104,12 @@ var collection = new InMemoryCollection("skhotels"); ## Overview +> [!WARNING] +> The In-Memory Vector Store has support for custom filters that can be expressed as Python lambda functions, these functions are +> executed in the same process as the main application, and therefore can execute arbitrary code. +> We filter for certain allowed operations, but you should not let filters be set by untrusted sources, including by LLM inputs. +> See the [Filtering](#filtering) section for more details. + The In-Memory Vector Store connector is a Vector Store implementation provided by Semantic Kernel that uses no external database and stores data in memory. This Vector Store is useful for prototyping scenarios or where high-speed in-memory operations are required. @@ -149,6 +155,53 @@ from semantic_kernel.connectors.in_memory import InMemoryCollection vector_collection = InMemoryCollection(record_type=DataModel, collection_name="collection_name") ``` +### Filtering + +> [!WARNING] +> The In-Memory Vector Store has support for custom filters that can be expressed as Python lambda functions, these functions are +> executed in the same process as the main application, and therefore can execute arbitrary code. +> We filter for certain allowed operations, but you should not let filters be set by untrusted sources, including by LLM inputs. + +The In-Memory connector uses an allowlist approach for filter security. Only the following operations are permitted in filter expressions: + +#### Allowed operations + +| Category | Allowed Operations | +|----------|-------------------| +| **Comparisons** | `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `is`, `is not` | +| **Boolean operations** | `and`, `or`, `not` | +| **Data access** | Attribute access (e.g., `x.field`), subscript access (e.g., `x['field']`), slicing | +| **Literals** | Constants, lists, tuples, sets, dictionaries | +| **Basic arithmetic** | `+`, `-`, `*`, `/`, `%`, `//` | + +#### Allowed functions + +The following built-in functions and methods can be used in filter expressions: + +- **Type conversion**: `str`, `int`, `float`, `bool` +- **Aggregation**: `len`, `abs`, `min`, `max`, `sum`, `any`, `all` +- **String methods**: `lower`, `upper`, `strip`, `startswith`, `endswith`, `contains` +- **Dictionary methods**: `get`, `keys`, `values`, `items` + +#### Examples + +```python +# Simple equality filter +results = await collection.search(vector, filter="lambda x: x.category == 'electronics'") + +# Numeric comparison +results = await collection.search(vector, filter="lambda x: x.price < 100") + +# Boolean combination +results = await collection.search(vector, filter="lambda x: x.in_stock and x.rating >= 4.0") + +# String method +results = await collection.search(vector, filter="lambda x: x.name.startswith('A')") + +# Membership test +results = await collection.search(vector, filter="lambda x: x.status in ['active', 'pending']") +``` + ::: zone-end ::: zone pivot="programming-language-java" From 318feb44ade7b6484f3d18d4f054b72388872578 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:45:22 +0000 Subject: [PATCH 05/12] Add an overview page for all integrations (#832) * Add an overview for all integrations * Fix warnings --- agent-framework/TOC.yml | 4 +- agent-framework/integrations/TOC.yml | 4 ++ agent-framework/integrations/index.md | 91 +++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 agent-framework/integrations/TOC.yml create mode 100644 agent-framework/integrations/index.md diff --git a/agent-framework/TOC.yml b/agent-framework/TOC.yml index 48270cbd..44583fd1 100644 --- a/agent-framework/TOC.yml +++ b/agent-framework/TOC.yml @@ -23,9 +23,7 @@ items: - name: Observability href: user-guide/observability.md - name: Integrations - items: - - name: AG-UI - href: integrations/ag-ui/TOC.yml + href: integrations/TOC.yml - name: Support href: support/TOC.yml - name: Migration Guide diff --git a/agent-framework/integrations/TOC.yml b/agent-framework/integrations/TOC.yml new file mode 100644 index 00000000..36a87680 --- /dev/null +++ b/agent-framework/integrations/TOC.yml @@ -0,0 +1,4 @@ +- name: Overview + href: index.md +- name: AG-UI + href: ag-ui/TOC.yml diff --git a/agent-framework/integrations/index.md b/agent-framework/integrations/index.md new file mode 100644 index 00000000..2e02839e --- /dev/null +++ b/agent-framework/integrations/index.md @@ -0,0 +1,91 @@ +--- +title: Agent Framework Integrations +description: Agent Framework Integrations +author: westey-m +ms.topic: conceptual +ms.author: westey +ms.date: 01/27/2026 +ms.service: agent-framework +zone_pivot_groups: programming-languages +--- + +# Agent Framework Integrations + +Microsoft Agent Framework has integrations with many different services, tools and protocols. + +## UI Framework integrations + +| UI Framework | Release Status | +| ------------------------------------------------------------------ | --------------- | +| [AG UI](./ag-ui/index.md) | Preview | +| [Agent Framework Dev UI](../user-guide/devui/index.md) | Preview | + +## Chat History Providers + +Microsoft Agent Framework supports many differenta agent types with different chat history storage capabilities. +In some cases agents store chat history in the AI service, while in others Agent Framework manages the storage. + +To allow chat history storage to be customized when managed by Agent Framework, custom Chat History Providers +may be supplied. Here is a list of existing providers that can be used. + +::: zone pivot="programming-language-csharp" + +| Chat History Provider | Release Status | +| ------------------------------------------------------------------ | --------------- | +| [In-Memory Chat History Provider](https://github.com/microsoft/agent-framework/blob/main/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryChatHistoryProvider.cs) | Preview | +| [Cosmos DB Chat History Provider](https://github.com/microsoft/agent-framework/blob/main/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatHistoryProvider.cs) | Preview | + +::: zone-end + +::: zone pivot="programming-language-python" + +| Chat Message Store | Release Status | +| ------------------------------------------------------------------ | --------------- | +| [Redis Chat Message Store](https://github.com/microsoft/agent-framework/blob/main/python/packages/redis/agent_framework_redis/_chat_message_store.py) | Preview | + +::: zone-end + +## Memory AI Context Providers + +AI Context Providers are plugins for `ChatClientAgent` instances and can be used to add memory to an agent. This is done by extracting memories from new messages provided by the user or generated by the agent, and by searching for existing memories and providing them to the AI service with the user input. + +Here is a list of existing providers that can be used. + +::: zone pivot="programming-language-csharp" + +| Memory AI Context Provider | Release Status | +| ------------------------------------------------------------------ | --------------- | +| [Chat History Memory Provider](https://github.com/microsoft/agent-framework/blob/main/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs) | Preview | + +::: zone-end + +::: zone pivot="programming-language-python" + +| Memory AI Context Provider | Release Status | +| ------------------------------------------------------------------ | --------------- | +| [Mem0 Memory Provider](https://github.com/microsoft/agent-framework/blob/main/python/packages/mem0/agent_framework_mem0/_provider.py) | Preview | +| [Redis Provider](https://github.com/microsoft/agent-framework/blob/main/python/packages/redis/agent_framework_redis/_provider.py) | Preview | + +::: zone-end + +## Retrieval Augmented Generation (RAG) AI Context Providers + +AI Context Providers are plugins for `ChatClientAgent` instances and can be used to add RAG capabilities to an agent. This is done by searching for relevant data based on the user input, and passing this data to the AI Service with the other inputs. + +Here is a list of existing providers that can be used. + +::: zone pivot="programming-language-csharp" + +| RAG AI Context Provider | Release Status | +| ------------------------------------------------------------------ | --------------- | +| [Text Search Provider](https://github.com/microsoft/agent-framework/blob/main/dotnet/src/Microsoft.Agents.AI/TextSearchProvider.cs) | Preview | + +::: zone-end + +::: zone pivot="programming-language-python" + +| RAG AI Context Provider | Release Status | +| ------------------------------------------------------------------ | --------------- | +| [Azure AI Search Provider](https://github.com/microsoft/agent-framework/blob/main/python/packages/azure-ai-search/agent_framework_azure_ai_search/_search_provider.py) | Preview | + +::: zone-end From e820d9b23da338e86a5bf3586376b6a3291e9500 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:06:16 +0000 Subject: [PATCH 06/12] Rename AgentThread to AgentSession (#830) --- .../ag-ui/backend-tool-rendering.md | 2 +- .../integrations/ag-ui/frontend-tools.md | 4 +- .../integrations/ag-ui/getting-started.md | 6 +- .../integrations/ag-ui/human-in-the-loop.md | 18 ++-- agent-framework/integrations/ag-ui/index.md | 6 +- .../ag-ui/security-considerations.md | 10 +- .../integrations/ag-ui/state-management.md | 14 +-- .../from-semantic-kernel/index.md | 20 ++-- .../overview/agent-framework-overview.md | 6 +- .../agents/function-tools-approvals.md | 8 +- agent-framework/tutorials/agents/memory.md | 30 +++--- .../tutorials/agents/middleware.md | 4 +- .../agents/multi-turn-conversation.md | 28 +++--- .../agents/persisted-conversation.md | 40 ++++---- .../third-party-chat-history-storage.md | 58 +++++------ agent-framework/user-guide/agents/TOC.yml | 2 +- .../agents/agent-background-responses.md | 12 +-- .../user-guide/agents/agent-memory.md | 56 +++++------ .../user-guide/agents/agent-middleware.md | 12 +-- .../agent-types/azure-ai-foundry-agent.md | 2 +- .../agents/agent-types/custom-agent.md | 76 +++++++++----- .../agent-types/durable-agent/features.md | 12 +-- .../agents/multi-turn-conversation.md | 98 +++++++++---------- .../user-guide/agents/running-agents.md | 2 +- agent-framework/user-guide/hosting/index.md | 6 +- .../using-mcp-with-foundry-agents.md | 4 +- .../user-guide/workflows/as-agents.md | 34 +++---- .../workflows/orchestrations/group-chat.md | 4 +- .../workflows/orchestrations/handoff.md | 4 +- .../user-guide/workflows/using-agents.md | 2 +- 30 files changed, 302 insertions(+), 278 deletions(-) diff --git a/agent-framework/integrations/ag-ui/backend-tool-rendering.md b/agent-framework/integrations/ag-ui/backend-tool-rendering.md index 3c0021e2..c8ed9d20 100644 --- a/agent-framework/integrations/ag-ui/backend-tool-rendering.md +++ b/agent-framework/integrations/ag-ui/backend-tool-rendering.md @@ -186,7 +186,7 @@ To see tool calls and results in real-time, extend the client's streaming loop t ```csharp // Inside the streaming loop from getting-started.md -await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread)) +await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session)) { ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate(); diff --git a/agent-framework/integrations/ag-ui/frontend-tools.md b/agent-framework/integrations/ag-ui/frontend-tools.md index 7fb3bb36..43afdab6 100644 --- a/agent-framework/integrations/ag-ui/frontend-tools.md +++ b/agent-framework/integrations/ag-ui/frontend-tools.md @@ -87,7 +87,7 @@ AIAgent inspectableAgent = baseAgent static async IAsyncEnumerable InspectToolsMiddleware( IEnumerable messages, - AgentThread? thread, + AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken) @@ -109,7 +109,7 @@ static async IAsyncEnumerable InspectToolsMiddleware( } } - await foreach (AgentResponseUpdate update in innerAgent.RunStreamingAsync(messages, thread, options, cancellationToken)) + await foreach (AgentResponseUpdate update in innerAgent.RunStreamingAsync(messages, session, options, cancellationToken)) { yield return update; } diff --git a/agent-framework/integrations/ag-ui/getting-started.md b/agent-framework/integrations/ag-ui/getting-started.md index e456bdc1..5c10d649 100644 --- a/agent-framework/integrations/ag-ui/getting-started.md +++ b/agent-framework/integrations/ag-ui/getting-started.md @@ -178,7 +178,7 @@ AIAgent agent = chatClient.AsAIAgent( name: "agui-client", description: "AG-UI Client Agent"); -AgentThread thread = await agent.GetNewThreadAsync(); +AgentSession session = await agent.GetNewSessionAsync(); List messages = [ new(ChatRole.System, "You are a helpful assistant.") @@ -209,7 +209,7 @@ try bool isFirstUpdate = true; string? threadId = null; - await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread)) + await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session)) { ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate(); @@ -259,7 +259,7 @@ catch (Exception ex) - **AsAIAgent**: Extension method on `AGUIChatClient` to create an agent from the client - **RunStreamingAsync**: Streams responses as `AgentResponseUpdate` objects - **AsChatResponseUpdate**: Extension method to access chat-specific properties like `ConversationId` and `ResponseId` -- **Thread Management**: The `AgentThread` maintains conversation context across requests +- **Session Management**: The `AgentSession` maintains conversation context across requests - **Content Types**: Responses include `TextContent` for messages and `ErrorContent` for errors ### Configure and Run the Client diff --git a/agent-framework/integrations/ag-ui/human-in-the-loop.md b/agent-framework/integrations/ag-ui/human-in-the-loop.md index 57a343d5..9cbd109e 100644 --- a/agent-framework/integrations/ag-ui/human-in-the-loop.md +++ b/agent-framework/integrations/ag-ui/human-in-the-loop.md @@ -116,10 +116,10 @@ var jsonOptions = app.Services.GetRequiredService + .Use(runFunc: null, runStreamingFunc: (messages, session, options, innerAgent, cancellationToken) => HandleApprovalRequestsMiddleware( messages, - thread, + session, options, innerAgent, jsonOptions.SerializerOptions, @@ -128,7 +128,7 @@ var agent = baseAgent static async IAsyncEnumerable HandleApprovalRequestsMiddleware( IEnumerable messages, - AgentThread? thread, + AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions, @@ -139,7 +139,7 @@ static async IAsyncEnumerable HandleApprovalRequestsMiddlew // Invoke inner agent await foreach (var update in innerAgent.RunStreamingAsync( - modifiedMessages, thread, options, cancellationToken)) + modifiedMessages, session, options, cancellationToken)) { // Process updates: Convert approval requests to client tool calls await foreach (var processedUpdate in ConvertFunctionApprovalsToToolCalls(update, jsonSerializerOptions)) @@ -327,10 +327,10 @@ var jsonSerializerOptions = JsonSerializerOptions.Default; // Wrap the agent with approval middleware var wrappedAgent = agent .AsBuilder() - .Use(runFunc: null, runStreamingFunc: (messages, thread, options, innerAgent, cancellationToken) => + .Use(runFunc: null, runStreamingFunc: (messages, session, options, innerAgent, cancellationToken) => HandleApprovalRequestsClientMiddleware( messages, - thread, + session, options, innerAgent, jsonSerializerOptions, @@ -339,7 +339,7 @@ var wrappedAgent = agent static async IAsyncEnumerable HandleApprovalRequestsClientMiddleware( IEnumerable messages, - AgentThread? thread, + AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions, @@ -349,7 +349,7 @@ static async IAsyncEnumerable HandleApprovalRequestsClientM var processedMessages = ConvertApprovalResponsesToToolResults(messages, jsonSerializerOptions); // Invoke inner agent - await foreach (var update in innerAgent.RunStreamingAsync(processedMessages, thread, options, cancellationToken)) + await foreach (var update in innerAgent.RunStreamingAsync(processedMessages, session, options, cancellationToken)) { // Process updates: Convert tool calls to approval requests await foreach (var processedUpdate in ConvertToolCallsToApprovalRequests(update, jsonSerializerOptions)) @@ -491,7 +491,7 @@ do approvalToolCalls.Clear(); await foreach (AgentResponseUpdate update in wrappedAgent.RunStreamingAsync( - messages, thread, cancellationToken: cancellationToken)) + messages, session, cancellationToken: cancellationToken)) { foreach (AIContent content in update.Contents) { diff --git a/agent-framework/integrations/ag-ui/index.md b/agent-framework/integrations/ag-ui/index.md index 4882bbc4..91e43b6f 100644 --- a/agent-framework/integrations/ag-ui/index.md +++ b/agent-framework/integrations/ag-ui/index.md @@ -20,7 +20,7 @@ AG-UI is a standardized protocol for building AI agent interfaces that provides: - **Remote Agent Hosting**: Deploy AI agents as web services accessible by multiple clients - **Real-time Streaming**: Stream agent responses using Server-Sent Events (SSE) for immediate feedback - **Standardized Communication**: Consistent message format for reliable agent interactions -- **Thread Management**: Maintain conversation context across multiple requests +- **Session Management**: Maintain conversation context across multiple requests - **Advanced Features**: Human-in-the-loop approvals, state synchronization, and custom UI rendering ## When to Use AG-UI @@ -65,7 +65,7 @@ While you can run agents directly in your application using Agent Framework's `R | Client Access | Single application | Multiple clients (web, mobile) | | Streaming | In-process async iteration | Server-Sent Events (SSE) | | State Management | Application-managed | Protocol-level state snapshots | -| Thread Context | Application-managed | Protocol-managed thread IDs | +| Session Context | Application-managed | Protocol-managed session IDs | | Approval Workflows | Custom implementation | Built-in middleware pattern | ## Architecture Overview @@ -117,7 +117,7 @@ Understanding how Agent Framework concepts map to AG-UI helps you build effectiv | `AgentResponseUpdate` | AG-UI Events | Converted to protocol events automatically | | `AIFunctionFactory.Create()` | Backend Tools | Executed on server, results streamed | | `ApprovalRequiredAIFunction` | Human-in-the-Loop | Middleware converts to approval protocol | -| `AgentThread` | Thread Management | `ConversationId` maintains context | +| `AgentSession` | Session Management | `ConversationId` maintains context | | `ChatResponseFormat.ForJsonSchema()` | State Snapshots | Structured output becomes state events | ## Installation diff --git a/agent-framework/integrations/ag-ui/security-considerations.md b/agent-framework/integrations/ag-ui/security-considerations.md index ee978d10..5bb362d1 100644 --- a/agent-framework/integrations/ag-ui/security-considerations.md +++ b/agent-framework/integrations/ag-ui/security-considerations.md @@ -150,14 +150,14 @@ Forwarded properties contain arbitrary JSON that passes through the system. Trea AG-UI does not include built-in authorization mechanism. It is up to your application to prevent unauthorized use of the exposed AG-UI endpoint. -### Thread ID Management +### Session ID Management -Thread IDs identify conversation sessions. Implement proper validation to prevent unauthorized access. +Session IDs identify conversation sessions. Implement proper validation to prevent unauthorized access. **Security considerations:** -- Generate thread IDs server-side using cryptographically secure random values -- Never allow clients to directly access arbitrary thread IDs -- Verify thread ownership before processing requests +- Generate Session IDs server-side using cryptographically secure random values +- Never allow clients to directly access arbitrary Session IDs +- Verify session ownership before processing requests ### Sensitive Data Filtering diff --git a/agent-framework/integrations/ag-ui/state-management.md b/agent-framework/integrations/ag-ui/state-management.md index 8a700c5e..4c26b96c 100644 --- a/agent-framework/integrations/ag-ui/state-management.md +++ b/agent-framework/integrations/ag-ui/state-management.md @@ -115,17 +115,17 @@ internal sealed class SharedStateAgent : DelegatingAIAgent public override Task RunAsync( IEnumerable messages, - AgentThread? thread = null, + AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { - return this.RunStreamingAsync(messages, thread, options, cancellationToken) + return this.RunStreamingAsync(messages, session, options, cancellationToken) .ToAgentResponseAsync(cancellationToken); } public override async IAsyncEnumerable RunStreamingAsync( IEnumerable messages, - AgentThread? thread = null, + AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { @@ -136,7 +136,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent state.ValueKind != JsonValueKind.Object) { // No state management requested, pass through to inner agent - await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false)) + await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false)) { yield return update; } @@ -154,7 +154,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent if (!hasProperties) { // Empty state - treat as no state - await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false)) + await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false)) { yield return update; } @@ -188,7 +188,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent // Collect all updates from first run var allUpdates = new List(); - await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, thread, firstRunOptions, cancellationToken).ConfigureAwait(false)) + await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, session, firstRunOptions, cancellationToken).ConfigureAwait(false)) { allUpdates.Add(update); @@ -225,7 +225,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent ChatRole.System, [new TextContent("Please provide a concise summary of the state changes in at most two sentences.")])); - await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, thread, options, cancellationToken).ConfigureAwait(false)) + await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, session, options, cancellationToken).ConfigureAwait(false)) { yield return update; } diff --git a/agent-framework/migration-guide/from-semantic-kernel/index.md b/agent-framework/migration-guide/from-semantic-kernel/index.md index a3d4ba51..25cade95 100644 --- a/agent-framework/migration-guide/from-semantic-kernel/index.md +++ b/agent-framework/migration-guide/from-semantic-kernel/index.md @@ -84,7 +84,7 @@ Additionally, for hosted agent providers you can also use the `GetAIAgent` metho AIAgent azureFoundryAgent = await persistentAgentsClient.GetAIAgentAsync(agentId); ``` -## 3. Agent Thread Creation +## 3. Agent Thread/Session Creation ### Semantic Kernel @@ -99,14 +99,14 @@ AgentThread thread = new OpenAIResponseAgentThread(this.Client); ### Agent Framework -The agent is responsible for creating the thread. +The agent is responsible for creating the session. ```csharp // New. -AgentThread thread = await agent.GetNewThreadAsync(); +AgentSession session = await agent.GetNewSessionAsync(); ``` -## 4. Hosted Agent Thread Cleanup +## 4. Hosted Agent Thread/Session Cleanup This case applies exclusively to a few AI providers that still provide hosted threads. @@ -123,16 +123,16 @@ await thread.DeleteAsync(); ### Agent Framework > [!NOTE] -> OpenAI Responses introduced a new conversation model that simplifies how conversations are handled. This change simplifies hosted thread management compared to the now deprecated OpenAI Assistants model. For more information, see the [OpenAI Assistants migration guide](https://platform.openai.com/docs/assistants/migration). +> OpenAI Responses introduced a new conversation model that simplifies how conversations are handled. This change simplifies hosted chat history management compared to the now deprecated OpenAI Assistants model. For more information, see the [OpenAI Assistants migration guide](https://platform.openai.com/docs/assistants/migration). -Agent Framework doesn't have a thread deletion API in the `AgentThread` type as not all providers support hosted threads or thread deletion. This design will become more common as more providers shift to responses-based architectures. +Agent Framework doesn't have a chat history or session deletion API in the `AgentSession` type as not all providers support hosted chat history or chat history deletion. -If you require thread deletion and the provider allows it, the caller **should** keep track of the created threads and delete them later when necessary via the provider's SDK. +If you require chat history deletion and the provider allows it, the caller **should** keep track of the created sessions and delete their associated chat hsitory later when necessary via the provider's SDK. OpenAI Assistants Provider: ```csharp -await assistantClient.DeleteThreadAsync(thread.ConversationId); +await assistantClient.DeleteThreadAsync(session.ConversationId); ``` ## 5. Tool Registration @@ -186,7 +186,7 @@ All messages created as part of the response are returned in the `AgentResponse. This might include tool call messages, function results, reasoning updates, and final results. ```csharp -AgentResponse agentResponse = await agent.RunAsync(userInput, thread); +AgentResponse agentResponse = await agent.RunAsync(userInput, session); ``` ## 7. Agent Streaming Invocation @@ -209,7 +209,7 @@ Agent Framework has a similar streaming API pattern, with the key difference bei All updates produced by any service underlying the AIAgent are returned. The textual result of the agent is available by concatenating the `AgentResponse.Text` values. ```csharp -await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(userInput, thread)) +await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(userInput, session)) { Console.Write(update); // Update is ToString() friendly } diff --git a/agent-framework/overview/agent-framework-overview.md b/agent-framework/overview/agent-framework-overview.md index 1978915b..e3458faf 100644 --- a/agent-framework/overview/agent-framework-overview.md +++ b/agent-framework/overview/agent-framework-overview.md @@ -22,7 +22,7 @@ Agent Framework offers two primary categories of capabilities: - [Workflows](#workflows): Graph-based workflows that connect multiple agents and functions to perform complex, multi-step tasks. Workflows support type-based routing, nesting, checkpointing, and request/response patterns for human-in-the-loop scenarios. The framework also provides foundational building -blocks, including model clients (chat completions and responses), an agent thread for state management, context providers for agent memory, +blocks, including model clients (chat completions and responses), an agent session for state management, context providers for agent memory, middleware for intercepting agent actions, and MCP clients for tool integration. Together, these components give you the flexibility and power to build interactive, robust, and safe AI applications. @@ -31,7 +31,7 @@ interactive, robust, and safe AI applications. [Semantic Kernel](https://github.com/microsoft/semantic-kernel) and [AutoGen](https://github.com/microsoft/autogen) pioneered the concepts of AI agents and multi-agent orchestration. -The Agent Framework is the direct successor, created by the same teams. It combines AutoGen's simple abstractions for single- and multi-agent patterns with Semantic Kernel's enterprise-grade features such as thread-based state management, type safety, filters, +The Agent Framework is the direct successor, created by the same teams. It combines AutoGen's simple abstractions for single- and multi-agent patterns with Semantic Kernel's enterprise-grade features such as session-based state management, type safety, filters, telemetry, and extensive model and embedding support. Beyond merging the two, Agent Framework introduces workflows that give developers explicit control over multi-agent execution paths, plus a robust state management system @@ -78,7 +78,7 @@ The following diagram illustrates the core components and their interactions in ![AI Agent Diagram](../media/agent.svg) An AI agent can also be augmented with additional components such as -a [thread](../user-guide/agents/multi-turn-conversation.md), +a [session](../user-guide/agents/multi-turn-conversation.md), a [context provider](../user-guide/agents/agent-memory.md), and [middleware](../user-guide/agents/agent-middleware.md) to enhance its capabilities. diff --git a/agent-framework/tutorials/agents/function-tools-approvals.md b/agent-framework/tutorials/agents/function-tools-approvals.md index a331e7cd..d75295d6 100644 --- a/agent-framework/tutorials/agents/function-tools-approvals.md +++ b/agent-framework/tutorials/agents/function-tools-approvals.md @@ -66,8 +66,8 @@ Since you now have a function that requires approval, the agent might respond wi You can check the response content for any `FunctionApprovalRequestContent` instances, which indicates that the agent requires user approval for a function. ```csharp -AgentThread thread = await agent.GetNewThreadAsync(); -AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", thread); +AgentSession session = await agent.GetNewSessionAsync(); +AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", session); var functionApprovalRequests = response.Messages .SelectMany(x => x.Contents) @@ -87,11 +87,11 @@ Console.WriteLine($"We require approval to execute '{requestContent.FunctionCall Once the user has provided their input, you can create a `FunctionApprovalResponseContent` instance using the `CreateResponse` method on the `FunctionApprovalRequestContent`. Pass `true` to approve the function call, or `false` to reject it. -The response content can then be passed to the agent in a new `User` `ChatMessage`, along with the same thread object to get the result back from the agent. +The response content can then be passed to the agent in a new `User` `ChatMessage`, along with the same session object to get the result back from the agent. ```csharp var approvalMessage = new ChatMessage(ChatRole.User, [requestContent.CreateResponse(true)]); -Console.WriteLine(await agent.RunAsync(approvalMessage, thread)); +Console.WriteLine(await agent.RunAsync(approvalMessage, session)); ``` Whenever you are using function tools with human in the loop approvals, remember to check for `FunctionApprovalRequestContent` instances in the response, after each agent run, until all function calls have been approved or rejected. diff --git a/agent-framework/tutorials/agents/memory.md b/agent-framework/tutorials/agents/memory.md index 0aa25f64..fa9459e2 100644 --- a/agent-framework/tutorials/agents/memory.md +++ b/agent-framework/tutorials/agents/memory.md @@ -23,7 +23,7 @@ For prerequisites and installing NuGet packages, see the [Create and run a simpl ## Create an AIContextProvider -`AIContextProvider` is an abstract class that you can inherit from, and which can be associated with the `AgentThread` for a `ChatClientAgent`. +`AIContextProvider` is an abstract class that you can inherit from, and which can be associated with the `AgentSession` for a `ChatClientAgent`. It allows you to: 1. Run custom logic before and after the agent invokes the underlying inference service. @@ -39,11 +39,11 @@ The `AIContextProvider` class has two methods that you can override to run custo ### Serialization -`AIContextProvider` instances are created and attached to an `AgentThread` when the thread is created, and when a thread is resumed from a serialized state. +`AIContextProvider` instances are created and attached to an `AgentSession` when the session is created, and when a session is resumed from a serialized state. The `AIContextProvider` instance might have its own state that needs to be persisted between invocations of the agent. For example, a memory component that remembers information about the user might have memories as part of its state. -To allow persisting threads, you need to implement the `SerializeAsync` method of the `AIContextProvider` class. You also need to provide a constructor that takes a `JsonElement` parameter, which can be used to deserialize the state when resuming a thread. +To allow persisting sessions, you need to implement the `SerializeAsync` method of the `AIContextProvider` class. You also need to provide a constructor that takes a `JsonElement` parameter, which can be used to deserialize the state when resuming a session. ### Sample AIContextProvider implementation @@ -62,10 +62,10 @@ internal sealed class UserInfo Then you can implement the `AIContextProvider` to manage the memories. The `UserInfoMemory` class below contains the following behavior: -1. It uses an `IChatClient` to look for the user's name and age in user messages when new messages are added to the thread at the end of each run. +1. It uses an `IChatClient` to look for the user's name and age in user messages when new messages are added to the session at the end of each run. 1. It provides any current memories to the agent before each invocation. 1. If no memories are available, it instructs the agent to ask the user for the missing information, and not to answer any questions until the information is provided. -1. It also implements serialization to allow persisting the memories as part of the thread state. +1. It also implements serialization to allow persisting the memories as part of the session state. ```csharp using System.Linq; @@ -142,7 +142,7 @@ internal sealed class UserInfoMemory : AIContextProvider ## Using the AIContextProvider with an agent -To use the custom `AIContextProvider`, you need to provide an `AIContextProviderFactory` when creating the agent. This factory allows the agent to create a new instance of the desired `AIContextProvider` for each thread. +To use the custom `AIContextProvider`, you need to provide an `AIContextProviderFactory` when creating the agent. This factory allows the agent to create a new instance of the desired `AIContextProvider` for each session. When creating a `ChatClientAgent` it is possible to provide a `ChatClientAgentOptions` object that allows providing the `AIContextProviderFactory` in addition to all other agent options. @@ -171,19 +171,19 @@ AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions() }); ``` -When creating a new thread, the `AIContextProvider` will be created by `GetNewThreadAsync` -and attached to the thread. Once memories are extracted it is therefore possible to access the memory component via the thread's `GetService` method and inspect the memories. +When creating a new session, the `AIContextProvider` will be created by `GetNewSessionAsync` +and attached to the session. Once memories are extracted it is therefore possible to access the memory component via the session's `GetService` method and inspect the memories. ```csharp -// Create a new thread for the conversation. -AgentThread thread = await agent.GetNewThreadAsync(); +// Create a new session for the conversation. +AgentSession session = await agent.GetNewSessionAsync(); -Console.WriteLine(await agent.RunAsync("Hello, what is the square root of 9?", thread)); -Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", thread)); -Console.WriteLine(await agent.RunAsync("I am 20 years old", thread)); +Console.WriteLine(await agent.RunAsync("Hello, what is the square root of 9?", session)); +Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", session)); +Console.WriteLine(await agent.RunAsync("I am 20 years old", session)); -// Access the memory component via the thread's GetService method. -var userInfo = thread.GetService()?.UserInfo; +// Access the memory component via the session's GetService method. +var userInfo = session.GetService()?.UserInfo; Console.WriteLine($"MEMORY - User Name: {userInfo?.UserName}"); Console.WriteLine($"MEMORY - User Age: {userInfo?.UserAge}"); ``` diff --git a/agent-framework/tutorials/agents/middleware.md b/agent-framework/tutorials/agents/middleware.md index c118e0e2..02852692 100644 --- a/agent-framework/tutorials/agents/middleware.md +++ b/agent-framework/tutorials/agents/middleware.md @@ -64,13 +64,13 @@ using System.Threading.Tasks; async Task CustomAgentRunMiddleware( IEnumerable messages, - AgentThread? thread, + AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken) { Console.WriteLine($"Input: {messages.Count()}"); - var response = await innerAgent.RunAsync(messages, thread, options, cancellationToken).ConfigureAwait(false); + var response = await innerAgent.RunAsync(messages, session, options, cancellationToken).ConfigureAwait(false); Console.WriteLine($"Output: {response.Messages.Count}"); return response; } diff --git a/agent-framework/tutorials/agents/multi-turn-conversation.md b/agent-framework/tutorials/agents/multi-turn-conversation.md index 05466ce3..df5d0966 100644 --- a/agent-framework/tutorials/agents/multi-turn-conversation.md +++ b/agent-framework/tutorials/agents/multi-turn-conversation.md @@ -27,37 +27,37 @@ For prerequisites and creating the agent, see the [Create and run a simple agent Agents are stateless and do not maintain any state internally between calls. To have a multi-turn conversation with an agent, you need to create an object to hold the conversation state and pass this object to the agent when running it. -To create the conversation state object, call the `GetNewThreadAsync` method on the agent instance. +To create the conversation state object, call the `GetNewSessionAsync` method on the agent instance. ```csharp -AgentThread thread = await agent.GetNewThreadAsync(); +AgentSession session = await agent.GetNewSessionAsync(); ``` -You can then pass this thread object to the `RunAsync` and `RunStreamingAsync` methods on the agent instance, along with the user input. +You can then pass this session object to the `RunAsync` and `RunStreamingAsync` methods on the agent instance, along with the user input. ```csharp -Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread)); -Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread)); +Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session)); +Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session)); ``` This will maintain the conversation state between the calls, and the agent will be able to refer to previous input and response messages in the conversation when responding to new input. > [!IMPORTANT] -> The type of service that is used by the `AIAgent` will determine how conversation history is stored. For example, when using a ChatCompletion service, like in this example, the conversation history is stored in the AgentThread object and sent to the service on each call. When using the Azure AI Agent service on the other hand, the conversation history is stored in the Azure AI Agent service and only a reference to the conversation is sent to the service on each call. +> The type of service that is used by the `AIAgent` will determine how chat history is stored. For example, when using a ChatCompletion service, like in this example, the chat history is stored in the AgentSession object and sent to the service on each call. When using the Azure AI Agent service on the other hand, the chat history is stored in the Azure AI Agent service and only a reference to the chat history is sent to the service on each call. ## Single agent with multiple conversations -It is possible to have multiple, independent conversations with the same agent instance, by creating multiple `AgentThread` objects. -These threads can then be used to maintain separate conversation states for each conversation. +It is possible to have multiple, independent conversations with the same agent instance, by creating multiple `AgentSession` objects. +These sessions can then be used to maintain separate conversation states for each conversation. The conversations will be fully independent of each other, since the agent does not maintain any state internally. ```csharp -AgentThread thread1 = await agent.GetNewThreadAsync(); -AgentThread thread2 = await agent.GetNewThreadAsync(); -Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread1)); -Console.WriteLine(await agent.RunAsync("Tell me a joke about a robot.", thread2)); -Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread1)); -Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a robot.", thread2)); +AgentSession session1 = await agent.GetNewSessionAsync(); +AgentSession session2 = await agent.GetNewSessionAsync(); +Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session1)); +Console.WriteLine(await agent.RunAsync("Tell me a joke about a robot.", session2)); +Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session1)); +Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a robot.", session2)); ``` ::: zone-end diff --git a/agent-framework/tutorials/agents/persisted-conversation.md b/agent-framework/tutorials/agents/persisted-conversation.md index 65d1ac10..530d697f 100644 --- a/agent-framework/tutorials/agents/persisted-conversation.md +++ b/agent-framework/tutorials/agents/persisted-conversation.md @@ -13,9 +13,9 @@ ms.service: agent-framework ::: zone pivot="programming-language-csharp" -This tutorial shows how to persist an agent conversation (AgentThread) to storage and reload it later. +This tutorial shows how to persist an agent conversation (AgentSession) to storage and reload it later. -When hosting an agent in a service or even in a client application, you often want to maintain conversation state across multiple requests or sessions. By persisting the `AgentThread`, you can save the conversation context and reload it later. +When hosting an agent in a service or even in a client application, you often want to maintain conversation state across multiple requests or sessions. By persisting the `AgentSession`, you can save the conversation context and reload it later. ## Prerequisites @@ -23,7 +23,7 @@ For prerequisites and installing NuGet packages, see the [Create and run a simpl ## Persisting and resuming the conversation -Create an agent and obtain a new thread that will hold the conversation state. +Create an agent and obtain a new session that will hold the conversation state. ```csharp using System; @@ -38,35 +38,35 @@ AIAgent agent = new AzureOpenAIClient( .GetChatClient("gpt-4o-mini") .AsAIAgent(instructions: "You are a helpful assistant.", name: "Assistant"); -AgentThread thread = await agent.GetNewThreadAsync(); +AgentSession session = await agent.GetNewSessionAsync(); ``` -Run the agent, passing in the thread, so that the `AgentThread` includes this exchange. +Run the agent, passing in the session, so that the `AgentSession` includes this exchange. ```csharp -// Run the agent and append the exchange to the thread -Console.WriteLine(await agent.RunAsync("Tell me a short pirate joke.", thread)); +// Run the agent and append the exchange to the session +Console.WriteLine(await agent.RunAsync("Tell me a short pirate joke.", session)); ``` -Call the `Serialize` method on the thread to serialize it to a JsonElement. +Call the `Serialize` method on the session to serialize it to a JsonElement. It can then be converted to a string for storage and saved to a database, blob storage, or file. ```csharp using System.IO; using System.Text.Json; -// Serialize the thread state -string serializedJson = thread.Serialize(JsonSerializerOptions.Web).GetRawText(); +// Serialize the session state +string serializedJson = session.Serialize(JsonSerializerOptions.Web).GetRawText(); // Example: save to a local file (replace with DB or blob storage in production) -string filePath = Path.Combine(Path.GetTempPath(), "agent_thread.json"); +string filePath = Path.Combine(Path.GetTempPath(), "agent_session.json"); await File.WriteAllTextAsync(filePath, serializedJson); ``` -Load the persisted JSON from storage and recreate the AgentThread instance from it. -The thread must be deserialized using an agent instance. This should be the -same agent type that was used to create the original thread. -This is because agents might have their own thread types and might construct threads with +Load the persisted JSON from storage and recreate the AgentSession instance from it. +The session must be deserialized using an agent instance. This should be the +same agent type that was used to create the original session. +This is because agents might have their own session types and might construct sessions with additional functionality that is specific to that agent type. ```csharp @@ -74,15 +74,15 @@ additional functionality that is specific to that agent type. string loadedJson = await File.ReadAllTextAsync(filePath); JsonElement reloaded = JsonSerializer.Deserialize(loadedJson, JsonSerializerOptions.Web); -// Deserialize the thread into an AgentThread tied to the same agent type -AgentThread resumedThread = await agent.DeserializeThreadAsync(reloaded, JsonSerializerOptions.Web); +// Deserialize the session into an AgentSession tied to the same agent type +AgentSession resumedSession = await agent.DeserializeSessionAsync(reloaded, JsonSerializerOptions.Web); ``` -Use the resumed thread to continue the conversation. +Use the resumed session to continue the conversation. ```csharp -// Continue the conversation with resumed thread -Console.WriteLine(await agent.RunAsync("Now tell that joke in the voice of a pirate.", resumedThread)); +// Continue the conversation with resumed session +Console.WriteLine(await agent.RunAsync("Now tell that joke in the voice of a pirate.", resumedSession)); ``` ::: zone-end diff --git a/agent-framework/tutorials/agents/third-party-chat-history-storage.md b/agent-framework/tutorials/agents/third-party-chat-history-storage.md index 7a14e9aa..c84b20d9 100644 --- a/agent-framework/tutorials/agents/third-party-chat-history-storage.md +++ b/agent-framework/tutorials/agents/third-party-chat-history-storage.md @@ -15,7 +15,7 @@ ms.service: agent-framework This tutorial shows how to store agent chat history in external storage by implementing a custom `ChatHistoryProvider` and using it with a `ChatClientAgent`. -By default, when using `ChatClientAgent`, chat history is stored either in memory in the `AgentThread` object or the underlying inference service, if the service supports it. +By default, when using `ChatClientAgent`, chat history is stored either in memory in the `AgentSession` object or the underlying inference service, if the service supports it. Where services do not require chat history to be stored in the service, it is possible to provide a custom store for persisting chat history instead of relying on the default in-memory behavior. @@ -56,11 +56,11 @@ Any chat history reduction logic, such as summarization or trimming, should be d ### Serialization -`ChatHistoryProvider` instances are created and attached to an `AgentThread` when the thread is created, and when a thread is resumed from a serialized state. +`ChatHistoryProvider` instances are created and attached to an `AgentSession` when the session is created, and when a session is resumed from a serialized state. While the actual messages making up the chat history are stored externally, the `ChatHistoryProvider` instance might need to store keys or other state to identify the chat history in the external store. -To allow persisting threads, you need to implement the `Serialize` method of the `ChatHistoryProvider` class. This method should return a `JsonElement` containing the state needed to restore the provider later. When deserializing, the agent framework will pass this serialized state to the ChatHistoryProviderFactory, allowing you to use it to recreate the provider. +To allow persisting sessions, you need to implement the `Serialize` method of the `ChatHistoryProvider` class. This method should return a `JsonElement` containing the state needed to restore the provider later. When deserializing, the agent framework will pass this serialized state to the ChatHistoryProviderFactory, allowing you to use it to recreate the provider. ### Sample ChatHistoryProvider implementation @@ -68,12 +68,12 @@ The following sample implementation stores chat messages in a vector store. `InvokedAsync` upserts messages into the vector store, using a unique key for each message. It stores both the request messages and response messages from the invocation context. -`InvokingAsync` retrieves the messages for the current thread from the vector store, orders them by timestamp, and returns them in ascending chronological order (oldest first). +`InvokingAsync` retrieves the messages for the current session from the vector store, orders them by timestamp, and returns them in ascending chronological order (oldest first). -When the first invocation occurs, the store generates a unique key for the thread, which is then used to identify the chat history in the vector store for subsequent calls. +When the first invocation occurs, the store generates a unique key for the session, which is then used to identify the chat history in the vector store for subsequent calls. -The unique key is stored in the `ThreadDbKey` property, which is serialized using the `Serialize` method and deserialized via the constructor that takes a `JsonElement`. -This key will therefore be persisted as part of the `AgentThread` state, allowing the thread to be resumed later and continue using the same chat history. +The unique key is stored in the `SessionDbKey` property, which is serialized using the `Serialize` method and deserialized via the constructor that takes a `JsonElement`. +This key will therefore be persisted as part of the `AgentSession` state, allowing the session to be resumed later and continue using the same chat history. ```csharp using System; @@ -99,19 +99,19 @@ internal sealed class VectorChatHistoryProvider : ChatHistoryProvider this._vectorStore = vectorStore ?? throw new ArgumentNullException(nameof(vectorStore)); if (serializedStoreState.ValueKind is JsonValueKind.String) { - this.ThreadDbKey = serializedStoreState.Deserialize(); + this.SessionDbKey = serializedStoreState.Deserialize(); } } - public string? ThreadDbKey { get; private set; } + public string? SessionDbKey { get; private set; } public override async ValueTask> InvokingAsync( InvokingContext context, CancellationToken cancellationToken = default) { - if (this.ThreadDbKey is null) + if (this.SessionDbKey is null) { - // No thread key yet, so no messages to retrieve + // No session key yet, so no messages to retrieve return []; } @@ -119,7 +119,7 @@ internal sealed class VectorChatHistoryProvider : ChatHistoryProvider await collection.EnsureCollectionExistsAsync(cancellationToken); var records = collection .GetAsync( - x => x.ThreadId == this.ThreadDbKey, + x => x.SessionId == this.SessionDbKey, 10, new() { OrderBy = x => x.Descending(y => y.Timestamp) }, cancellationToken); @@ -145,7 +145,7 @@ internal sealed class VectorChatHistoryProvider : ChatHistoryProvider return; } - this.ThreadDbKey ??= Guid.NewGuid().ToString("N"); + this.SessionDbKey ??= Guid.NewGuid().ToString("N"); var collection = this._vectorStore.GetCollection("ChatHistory"); await collection.EnsureCollectionExistsAsync(cancellationToken); @@ -157,24 +157,24 @@ internal sealed class VectorChatHistoryProvider : ChatHistoryProvider await collection.UpsertAsync(allNewMessages.Select(x => new ChatHistoryItem() { - Key = this.ThreadDbKey + x.MessageId, + Key = this.SessionDbKey + x.MessageId, Timestamp = DateTimeOffset.UtcNow, - ThreadId = this.ThreadDbKey, + SessionId = this.SessionDbKey, SerializedMessage = JsonSerializer.Serialize(x), MessageText = x.Text }), cancellationToken); } public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) => - // We have to serialize the thread id, so that on deserialization you can retrieve the messages using the same thread id. - JsonSerializer.SerializeToElement(this.ThreadDbKey); + // We have to serialize the session id, so that on deserialization you can retrieve the messages using the same session id. + JsonSerializer.SerializeToElement(this.SessionDbKey); private sealed class ChatHistoryItem { [VectorStoreKey] public string? Key { get; set; } [VectorStoreData] - public string? ThreadId { get; set; } + public string? SessionId { get; set; } [VectorStoreData] public DateTimeOffset? Timestamp { get; set; } [VectorStoreData] @@ -187,7 +187,7 @@ internal sealed class VectorChatHistoryProvider : ChatHistoryProvider ## Using the custom ChatHistoryProvider with a ChatClientAgent -To use the custom `ChatHistoryProvider`, you need to provide a `ChatHistoryProviderFactory` when creating the agent. This factory allows the agent to create a new instance of the desired `ChatHistoryProvider` for each thread. +To use the custom `ChatHistoryProvider`, you need to provide a `ChatHistoryProviderFactory` when creating the agent. This factory allows the agent to create a new instance of the desired `ChatHistoryProvider` for each session. When creating a `ChatClientAgent` it is possible to provide a `ChatClientAgentOptions` object that allows providing the `ChatHistoryProviderFactory` in addition to all other agent options. @@ -212,25 +212,25 @@ AIAgent agent = new AzureOpenAIClient( ChatOptions = new() { Instructions = "You are good at telling jokes." }, ChatHistoryProviderFactory = (ctx, ct) => new ValueTask( // Create a new chat history provider for this agent that stores the messages in a vector store. - // Each thread must get its own copy of the VectorChatHistoryProvider, since the provider - // also contains the id that the thread is stored under. + // Each session must get its own copy of the VectorChatHistoryProvider, since the provider + // also contains the id that the session is stored under. new VectorChatHistoryProvider( vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions)) }); -// Start a new thread for the agent conversation. -AgentThread thread = await agent.GetNewThreadAsync(); +// Start a new session for the agent conversation. +AgentSession session = await agent.GetNewSessionAsync(); -// Run the agent with the thread -var response = await agent.RunAsync("Tell me a joke about a pirate.", thread); +// Run the agent with the session +var response = await agent.RunAsync("Tell me a joke about a pirate.", session); -// The thread state can be serialized for storage -JsonElement serializedThread = thread.Serialize(); +// The session state can be serialized for storage +JsonElement serializedSession = session.Serialize(); -// Later, deserialize the thread to resume the conversation -AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread); +// Later, deserialize the session to resume the conversation +AgentSession resumedSession = await agent.DeserializeSessionAsync(serializedSession); ``` ::: zone-end diff --git a/agent-framework/user-guide/agents/TOC.yml b/agent-framework/user-guide/agents/TOC.yml index 8a6d59c0..13450ae2 100644 --- a/agent-framework/user-guide/agents/TOC.yml +++ b/agent-framework/user-guide/agents/TOC.yml @@ -2,7 +2,7 @@ href: agent-types/TOC.yml - name: Running Agents href: running-agents.md -- name: Multi-Turn Conversations and Threading +- name: Multi-Turn Conversations href: multi-turn-conversation.md - name: Agent Chat History and Memory href: agent-memory.md diff --git a/agent-framework/user-guide/agents/agent-background-responses.md b/agent-framework/user-guide/agents/agent-background-responses.md index f54dc109..c6774a2f 100644 --- a/agent-framework/user-guide/agents/agent-background-responses.md +++ b/agent-framework/user-guide/agents/agent-background-responses.md @@ -66,10 +66,10 @@ AgentRunOptions options = new() AllowBackgroundResponses = true }; -AgentThread thread = await agent.GetNewThreadAsync(); +AgentSession session = await agent.GetNewSessionAsync(); // Get initial response - may return with or without a continuation token -AgentResponse response = await agent.RunAsync("Write a very long novel about otters in space.", thread, options); +AgentResponse response = await agent.RunAsync("Write a very long novel about otters in space.", session, options); // Continue to poll until the final response is received while (response.ContinuationToken is not null) @@ -78,7 +78,7 @@ while (response.ContinuationToken is not null) await Task.Delay(TimeSpan.FromSeconds(2)); options.ContinuationToken = response.ContinuationToken; - response = await agent.RunAsync(thread, options); + response = await agent.RunAsync(session, options); } Console.WriteLine(response.Text); @@ -108,11 +108,11 @@ AgentRunOptions options = new() AllowBackgroundResponses = true }; -AgentThread thread = await agent.GetNewThreadAsync(); +AgentSession session = await agent.GetNewSessionAsync(); AgentResponseUpdate? latestReceivedUpdate = null; -await foreach (var update in agent.RunStreamingAsync("Write a very long novel about otters in space.", thread, options)) +await foreach (var update in agent.RunStreamingAsync("Write a very long novel about otters in space.", session, options)) { Console.Write(update.Text); @@ -124,7 +124,7 @@ await foreach (var update in agent.RunStreamingAsync("Write a very long novel ab // Resume from interruption point captured by the continuation token options.ContinuationToken = latestReceivedUpdate?.ContinuationToken; -await foreach (var update in agent.RunStreamingAsync(thread, options)) +await foreach (var update in agent.RunStreamingAsync(session, options)) { Console.Write(update.Text); } diff --git a/agent-framework/user-guide/agents/agent-memory.md b/agent-framework/user-guide/agents/agent-memory.md index 9bee58ac..cbb242ce 100644 --- a/agent-framework/user-guide/agents/agent-memory.md +++ b/agent-framework/user-guide/agents/agent-memory.md @@ -21,31 +21,31 @@ Various chat history storage options are supported by Agent Framework. The avail The two main supported scenarios are: -- **In-memory storage**: Agent is built on a service that doesn't support in-service storage of chat history (for example, OpenAI Chat Completion). By default, Agent Framework stores the full chat history in-memory in the `AgentThread` object, but developers can provide a custom `ChatHistoryProvider` implementation to store chat history in a third-party store if required. -- **In-service storage**: Agent is built on a service that requires in-service storage of chat history (for example, Azure AI Foundry Persistent Agents). Agent Framework stores the ID of the remote chat history in the `AgentThread` object, and no other chat history storage options are supported. +- **In-memory storage**: Agent is built on a service that doesn't support in-service storage of chat history (for example, OpenAI Chat Completion). By default, Agent Framework stores the full chat history in-memory in the `AgentSession` object, but developers can provide a custom `ChatHistoryProvider` implementation to store chat history in a third-party store if required. +- **In-service storage**: Agent is built on a service that requires in-service storage of chat history (for example, Azure AI Foundry Persistent Agents). Agent Framework stores the ID of the remote chat history in the `AgentSession` object, and no other chat history storage options are supported. ### In-memory chat history storage -When using a service that doesn't support in-service storage of chat history, Agent Framework defaults to storing chat history in-memory in the `AgentThread` object. In this case, the full chat history that's stored in the thread object, plus any new messages, will be provided to the underlying service on each agent run. This design allows for a natural conversational experience with the agent. The caller only provides the new user message, and the agent only returns new answers. But the agent has access to the full conversation history and will use it when generating its response. +When using a service that doesn't support in-service storage of chat history, Agent Framework defaults to storing chat history in-memory in the `AgentSession` object. In this case, the full chat history that's stored in the session object, plus any new messages, will be provided to the underlying service on each agent run. This design allows for a natural conversational experience with the agent. The caller only provides the new user message, and the agent only returns new answers. But the agent has access to the full conversation history and will use it when generating its response. -When using OpenAI Chat Completion as the underlying service for agents, the following code results in the thread object containing the chat history from the agent run. +When using OpenAI Chat Completion as the underlying service for agents, the following code results in the session object containing the chat history from the agent run. ```csharp AIAgent agent = new OpenAIClient("") .GetChatClient(modelName) .AsAIAgent(JokerInstructions, JokerName); -AgentThread thread = await agent.GetNewThreadAsync(); -Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread)); +AgentSession session = await agent.GetNewSessionAsync(); +Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session)); ``` -Where messages are stored in memory, it's possible to retrieve the list of messages from the thread and manipulate the messages directly if required. +Where messages are stored in memory, it's possible to retrieve the list of messages from the session and manipulate the messages directly if required. ```csharp -IList? messages = thread.GetService>(); +IList? messages = session.GetService>(); ``` > [!NOTE] -> Retrieving messages from the `AgentThread` object in this way only works if in-memory storage is being used. +> Retrieving messages from the `AgentSession` object in this way only works if in-memory storage is being used. #### Chat history reduction with in-memory storage @@ -58,7 +58,7 @@ It also allows you to configure the event during which the reducer is invoked, e or before the chat history is returned for the next invocation. To configure the `InMemoryChatHistoryProvider` with a reducer, you can provide a factory to construct a new `InMemoryChatHistoryProvider` -for each new `AgentThread` and pass it a reducer of your choice. The `InMemoryChatHistoryProvider` can also be passed an optional trigger event +for each new `AgentSession` and pass it a reducer of your choice. The `InMemoryChatHistoryProvider` can also be passed an optional trigger event which can be set to either `InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded` or `InMemoryChatHistoryProvider.ChatReducerTriggerEvent.BeforeMessagesRetrieval`. The factory is an async function that receives a context object and a cancellation token. @@ -84,16 +84,16 @@ AIAgent agent = new OpenAIClient("") ### Inference service chat history storage -When using a service that requires in-service storage of chat history, Agent Framework stores the ID of the remote chat history in the `AgentThread` object. +When using a service that requires in-service storage of chat history, Agent Framework stores the ID of the remote chat history in the `AgentSession` object. -For example, when using OpenAI Responses with store=true as the underlying service for agents, the following code will result in the thread object containing the last response ID returned by the service. +For example, when using OpenAI Responses with store=true as the underlying service for agents, the following code will result in the session object containing the last response ID returned by the service. ```csharp AIAgent agent = new OpenAIClient("") .GetOpenAIResponseClient(modelName) .AsAIAgent(JokerInstructions, JokerName); -AgentThread thread = await agent.GetNewThreadAsync(); -Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread)); +AgentSession session = await agent.GetNewSessionAsync(); +Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session)); ``` > [!NOTE] @@ -108,7 +108,7 @@ The `ChatHistoryProvider` class defines the interface for storing and retrieving The agent will use all messages returned by `InvokingAsync` when processing a user query. It is up to the implementer of `ChatHistoryProvider` to ensure that the size of the chat history does not exceed the context window of the underlying service. -When implementing a custom `ChatHistoryProvider` which stores chat history in a remote store, the chat history for that thread should be stored under a key that is unique to that thread. The `ChatHistoryProvider` implementation should generate this key and keep it in its state. `ChatHistoryProvider` has a `Serialize` method that can be overridden to serialize its state when the thread is serialized. The `ChatHistoryProvider` should also provide a constructor that takes a as input to support deserialization of its state. +When implementing a custom `ChatHistoryProvider` which stores chat history in a remote store, the chat history for that session should be stored under a key that is unique to that session. The `ChatHistoryProvider` implementation should generate this key and keep it in its state. `ChatHistoryProvider` has a `Serialize` method that can be overridden to serialize its state when the session is serialized. The `ChatHistoryProvider` should also provide a constructor that takes a as input to support deserialization of its state. To supply a custom `ChatHistoryProvider` to a `ChatClientAgent`, you can use the `ChatHistoryProviderFactory` option when creating the agent. Here is an example showing how to pass the custom implementation of `ChatHistoryProvider` to a `ChatClientAgent` that is based on Azure OpenAI Chat Completion. @@ -126,8 +126,8 @@ AIAgent agent = new AzureOpenAIClient( ChatOptions = new() { Instructions = JokerInstructions }, ChatHistoryProviderFactory = (ctx, ct) => new ValueTask( // Create a new chat history provider for this agent that stores the messages in a custom store. - // Each thread must get its own copy of the CustomChatHistoryProvider, since the provider - // also contains the ID that the thread is stored under. + // Each session must get its own copy of the CustomChatHistoryProvider, since the provider + // also contains the ID that the session is stored under. new CustomChatHistoryProvider( vectorStore, ctx.SerializedState, @@ -147,28 +147,28 @@ To implement such a memory component, the developer needs to subclass the `AICon > [!TIP] > For a detailed example on how to create a custom memory component, see the [Adding Memory to an Agent](../../tutorials/agents/memory.md) tutorial. -## AgentThread Serialization +## AgentSession Serialization -It is important to be able to persist an `AgentThread` object between agent invocations. This allows for situations where a user might ask a question of the agent, and take a long time to ask follow up questions. This allows the `AgentThread` state to survive service or app restarts. +It is important to be able to persist an `AgentSession` object between agent invocations. This allows for situations where a user might ask a question of the agent, and take a long time to ask follow up questions. This allows the `AgentSession` state to survive service or app restarts. -Even if the chat history is stored in a remote store, the `AgentThread` object still contains an ID referencing the remote chat history. Losing the `AgentThread` state will therefore result in also losing the ID of the remote chat history. +Even if the chat history is stored in a remote store, the `AgentSession` object still contains an ID referencing the remote chat history. Losing the `AgentSession` state will therefore result in also losing the ID of the remote chat history. -The `AgentThread` as well as any objects attached to it, all therefore provide the `Serialize` method to serialize their state. The `AIAgent` also provides a `DeserializeThreadAsync` method that re-creates a thread from the serialized state. The `DeserializeThreadAsync` method re-creates the thread with the `ChatHistoryProvider` and `AIContextProvider` configured on the agent. +The `AgentSession` as well as any objects attached to it, all therefore provide the `Serialize` method to serialize their state. The `AIAgent` also provides a `DeserializeSessionAsync` method that re-creates a session from the serialized state. The `DeserializeSessionAsync` method re-creates the session with the `ChatHistoryProvider` and `AIContextProvider` configured on the agent. ```csharp -// Serialize the thread state to a JsonElement, so it can be stored for later use. -JsonElement serializedThreadState = thread.Serialize(); +// Serialize the session state to a JsonElement, so it can be stored for later use. +JsonElement serializedSessionState = session.Serialize(); -// Re-create the thread from the JsonElement. -AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThreadState); +// Re-create the session from the JsonElement. +AgentSession resumedSession = await agent.DeserializeSessionAsync(serializedSessionState); ``` > [!NOTE] -> `AgentThread` objects may contain more than just chat history, e.g. context providers may also store state in the thread object. Therefore, it is important to always serialize, store and deserialize the entire `AgentThread` object to ensure that all state is preserved. +> `AgentSession` objects may contain more than just chat history, e.g. context providers may also store state in the session object. Therefore, it is important to always serialize, store and deserialize the entire `AgentSession` object to ensure that all state is preserved. > [!IMPORTANT] -> Always treat `AgentThread` objects as opaque objects, unless you are very sure of the internals. The contents may vary not just by agent type, but also by service type and configuration. +> Always treat `AgentSession` objects as opaque objects, unless you are very sure of the internals. The contents may vary not just by agent type, but also by service type and configuration. > [!WARNING] -> Deserializing a thread with a different agent than that which originally created it, or with an agent that has a different configuration than the original agent, might result in errors or unexpected behavior. +> Deserializing a session with a different agent than that which originally created it, or with an agent that has a different configuration than the original agent, might result in errors or unexpected behavior. ::: zone-end ::: zone pivot="programming-language-python" diff --git a/agent-framework/user-guide/agents/agent-middleware.md b/agent-framework/user-guide/agents/agent-middleware.md index 7b790695..ebff263a 100644 --- a/agent-framework/user-guide/agents/agent-middleware.md +++ b/agent-framework/user-guide/agents/agent-middleware.md @@ -74,13 +74,13 @@ Here is an example of agent run middleware, that can inspect and/or modify the i ```csharp async Task CustomAgentRunMiddleware( IEnumerable messages, - AgentThread? thread, + AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken) { Console.WriteLine(messages.Count()); - var response = await innerAgent.RunAsync(messages, thread, options, cancellationToken).ConfigureAwait(false); + var response = await innerAgent.RunAsync(messages, session, options, cancellationToken).ConfigureAwait(false); Console.WriteLine(response.Messages.Count); return response; } @@ -93,14 +93,14 @@ Here is an example of agent run streaming middleware, that can inspect and/or mo ```csharp async IAsyncEnumerable CustomAgentRunStreamingMiddleware( IEnumerable messages, - AgentThread? thread, + AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, [EnumeratorCancellation] CancellationToken cancellationToken) { Console.WriteLine(messages.Count()); List updates = []; - await foreach (var update in innerAgent.RunStreamingAsync(messages, thread, options, cancellationToken)) + await foreach (var update in innerAgent.RunStreamingAsync(messages, session, options, cancellationToken)) { updates.Add(update); yield return update; @@ -137,8 +137,8 @@ This will prevent the function calling loop from issuing a request to the infere If there were more than one function available for invocation during this iteration, it might also prevent any remaining functions from being executed. > [!WARNING] -> Terminating the function call loop might result in your thread being left in an inconsistent state, for example, containing function call content with no function result content. -> This might result in the thread being unusable for further runs. +> Terminating the function call loop might result in your chat history being left in an inconsistent state, for example, containing function call content with no function result content. +> This might result in the chat history being unusable for further runs. ## IChatClient middleware diff --git a/agent-framework/user-guide/agents/agent-types/azure-ai-foundry-agent.md b/agent-framework/user-guide/agents/agent-types/azure-ai-foundry-agent.md index 15f809f8..05a60902 100644 --- a/agent-framework/user-guide/agents/agent-types/azure-ai-foundry-agent.md +++ b/agent-framework/user-guide/agents/agent-types/azure-ai-foundry-agent.md @@ -11,7 +11,7 @@ ms.service: agent-framework # Azure AI Foundry Agents -Microsoft Agent Framework supports creating agents that use the [Azure AI Foundry Agents](/azure/ai-foundry/agents/overview) service. You can create persistent service-based agent instances with service-managed conversation threads. +Microsoft Agent Framework supports creating agents that use the [Azure AI Foundry Agents](/azure/ai-foundry/agents/overview) service. You can create persistent service-based agent instances with service-managed chat history. ::: zone pivot="programming-language-csharp" diff --git a/agent-framework/user-guide/agents/agent-types/custom-agent.md b/agent-framework/user-guide/agents/agent-types/custom-agent.md index c52e625e..793a34f9 100644 --- a/agent-framework/user-guide/agents/agent-types/custom-agent.md +++ b/agent-framework/user-guide/agents/agent-types/custom-agent.md @@ -28,24 +28,24 @@ dotnet add package Microsoft.Agents.AI.Abstractions --prerelease ## Create a Custom Agent -### The Agent Thread +### The Agent Session -To create a custom agent you also need a thread, which is used to keep track of the state +To create a custom agent you also need a session, which is used to keep track of the state of a single conversation, including message history, and any other state the agent needs to maintain. -To make it easy to get started, you can inherit from various base classes that implement common thread storage mechanisms. +To make it easy to get started, you can inherit from various base classes that implement common session storage mechanisms. -1. `InMemoryAgentThread` - stores the chat history in memory and can be serialized to JSON. -1. `ServiceIdAgentThread` - doesn't store any chat history, but allows you to associate an ID with the thread, under which the chat history can be stored externally. +1. `InMemoryAgentSession` - stores the chat history in memory and can be serialized to JSON. +1. `ServiceIdAgentSession` - doesn't store any chat history, but allows you to associate an ID with the session, under which the chat history can be stored externally. -For this example, you'll use the `InMemoryAgentThread` as the base class for the custom thread. +For this example, you'll use the `InMemoryAgentSession` as the base class for the custom session. ```csharp -internal sealed class CustomAgentThread : InMemoryAgentThread +internal sealed class CustomAgentSession : InMemoryAgentSession { - internal CustomAgentThread() : base() { } - internal CustomAgentThread(JsonElement serializedThreadState, JsonSerializerOptions? jsonSerializerOptions = null) - : base(serializedThreadState, jsonSerializerOptions) { } + internal CustomAgentSession() : base() { } + internal CustomAgentSession(JsonElement serializedSessionState, JsonSerializerOptions? jsonSerializerOptions = null) + : base(serializedSessionState, jsonSerializerOptions) { } } ``` @@ -59,20 +59,20 @@ internal sealed class UpperCaseParrotAgent : AIAgent } ``` -### Constructing threads +### Constructing sessions -Threads are always created via two factory methods on the agent class. -This allows for the agent to control how threads are created and deserialized. -Agents can therefore attach any additional state or behaviors needed to the thread when constructed. +Sessions are always created via two factory methods on the agent class. +This allows for the agent to control how sessions are created and deserialized. +Agents can therefore attach any additional state or behaviors needed to the session when constructed. Two methods are required to be implemented: ```csharp - public override Task GetNewThreadAsync(CancellationToken cancellationToken = default) - => Task.FromResult(new CustomAgentThread()); + public override Task GetNewSessionAsync(CancellationToken cancellationToken = default) + => Task.FromResult(new CustomAgentSession()); - public override Task DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) - => Task.FromResult(new CustomAgentThread(serializedThread, jsonSerializerOptions)); + public override Task DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => Task.FromResult(new CustomAgentSession(serializedSession, jsonSerializerOptions)); ``` ### Core agent logic @@ -104,16 +104,28 @@ The input messages are cloned, since various aspects of the input messages have Finally, you need to implement the two core methods that are used to run the agent: one for non-streaming and one for streaming. -For both methods, you need to ensure that a thread is provided, and if not, create a new thread. -The thread can then be updated with the new messages by calling `NotifyThreadOfNewMessagesAsync`. +For both methods, you need to ensure that a session is provided, and if not, create a new session. +Messages can be retrieved and passed to the `ChatHistoryProvider` on the session. If you don't do this, the user won't be able to have a multi-turn conversation with the agent and each run will be a fresh interaction. ```csharp - public override async Task RunAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + public override async Task RunAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { - thread ??= await this.GetNewThreadAsync(cancellationToken); + session ??= await this.GetNewSessionAsync(cancellationToken); + + // Get existing messages from the store + var invokingContext = new ChatHistoryProvider.InvokingContext(messages); + var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken); + List responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList(); - await NotifyThreadOfNewMessagesAsync(thread, messages.Concat(responseMessages), cancellationToken); + + // Notify the session of the input and output messages. + var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages) + { + ResponseMessages = responseMessages + }; + await typedSession.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken); + return new AgentResponse { AgentId = this.Id, @@ -122,11 +134,23 @@ If you don't do this, the user won't be able to have a multi-turn conversation w }; } - public override async IAsyncEnumerable RunStreamingAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public override async IAsyncEnumerable RunStreamingAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - thread ??= await this.GetNewThreadAsync(cancellationToken); + session ??= await this.GetNewSessionAsync(cancellationToken); + + // Get existing messages from the store + var invokingContext = new ChatHistoryProvider.InvokingContext(messages); + var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken); + List responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList(); - await NotifyThreadOfNewMessagesAsync(thread, messages.Concat(responseMessages), cancellationToken); + + // Notify the session of the input and output messages. + var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages) + { + ResponseMessages = responseMessages + }; + await typedSession.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken); + foreach (var message in responseMessages) { yield return new AgentResponseUpdate diff --git a/agent-framework/user-guide/agents/agent-types/durable-agent/features.md b/agent-framework/user-guide/agents/agent-types/durable-agent/features.md index c0abdd51..f8a41574 100644 --- a/agent-framework/user-guide/agents/agent-types/durable-agent/features.md +++ b/agent-framework/user-guide/agents/agent-types/durable-agent/features.md @@ -42,11 +42,11 @@ public static async Task SpamDetectionOrchestration( // Check if the email is spam DurableAIAgent spamDetectionAgent = context.GetAgent("SpamDetectionAgent"); - AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync(); + AgentSession spamSession = await spamDetectionAgent.GetNewSessionAsync(); AgentResponse spamDetectionResponse = await spamDetectionAgent.RunAsync( message: $"Analyze this email for spam: {email.EmailContent}", - thread: spamThread); + session: spamSession); DetectionResult result = spamDetectionResponse.Result; if (result.IsSpam) @@ -56,11 +56,11 @@ public static async Task SpamDetectionOrchestration( // Generate response for legitimate email DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent"); - AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync(); + AgentSession emailSession = await emailAssistantAgent.GetNewSessionAsync(); AgentResponse emailAssistantResponse = await emailAssistantAgent.RunAsync( message: $"Draft a professional response to: {email.EmailContent}", - thread: emailThread); + session: emailSession); return await context.CallActivityAsync(nameof(SendEmail), emailAssistantResponse.Result.Response); } @@ -345,9 +345,9 @@ Human-in-the-loop workflows with durable agents are extremely cost-effective whe The [Durable Task Scheduler](/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler) (DTS) is the recommended durable backend for your durable agents, offering the best performance, fully managed infrastructure, and built-in observability through a UI dashboard. While Azure Functions can use other storage backends (like Azure Storage), DTS is optimized specifically for durable workloads and provides superior performance and monitoring capabilities. -### Agent Thread Insights +### Agent Session Insights -- **Conversation history**: View complete conversation threads for each agent thread, including all messages, tool calls, and conversation context at any point in time +- **Conversation history**: View complete chat history for each agent session, including all messages, tool calls, and conversation context at any point in time - **Task timing**: Monitor how long specific tasks and agent interactions take to complete :::image type="content" source="../../../../media/durable-agent-chat-history.png" alt-text="Screenshot of the Durable Task Scheduler dashboard showing agent chat history with conversation threads and messages."::: diff --git a/agent-framework/user-guide/agents/multi-turn-conversation.md b/agent-framework/user-guide/agents/multi-turn-conversation.md index 3ffc6206..ed4f8b45 100644 --- a/agent-framework/user-guide/agents/multi-turn-conversation.md +++ b/agent-framework/user-guide/agents/multi-turn-conversation.md @@ -1,7 +1,7 @@ --- -title: Microsoft Agent Framework Multi-Turn Conversations and Threading +title: Microsoft Agent Framework Multi-Turn Conversations titleSuffix: Azure AI Foundry -description: Learn Agent Framework Multi-Turn Conversations and Threading. +description: Learn Agent Framework Multi-Turn Conversations ms.service: agent-framework ms.topic: tutorial ms.date: 09/04/2025 @@ -11,94 +11,94 @@ author: TaoChenOSU ms.author: taochen --- -# Microsoft Agent Framework Multi-Turn Conversations and Threading +# Microsoft Agent Framework Multi-Turn Conversations -The Microsoft Agent Framework provides built-in support for managing multi-turn conversations with AI agents. This includes maintaining context across multiple interactions. Different agent types and underlying services that are used to build agents might support different threading types, and Agent Framework abstracts these differences away, providing a consistent interface for developers. +The Microsoft Agent Framework provides built-in support for managing multi-turn conversations with AI agents. This includes maintaining context across multiple interactions. Different agent types and underlying services that are used to build agents might support different chat history storage types, and Agent Framework abstracts these differences away, providing a consistent interface for developers. -For example, when using a ChatClientAgent based on a foundry agent, the conversation history is persisted in the service. While, when using a ChatClientAgent based on chat completion with gpt-4.1 the conversation history is in-memory and managed by the agent. +For example, when using a ChatClientAgent based on a foundry agent, the chat history is persisted in the service. While, when using a ChatClientAgent based on chat completion with gpt-4.1 the chat history is in-memory and managed by the agent. -The `AgentThread` type is the abstraction that represents conversation history and other state of an agent. -`AIAgent` instances are stateless and the same agent instance can be used with multiple `AgentThread` instances. All state is therefore preserved in the `AgentThread`. -An `AgentThread` can both represent chat history plus any other state that the agent needs to preserve across multiple interactions. -The conversation history may be stored in the `AgentThread` object itself, or remotely, with the `AgentThread` only containing a reference to the remote conversation history. -The `AgentThread` state may also include memories or references to memories stored remotely. +The `AgentSession` type is the abstraction that represents chat history and other state of an agent. +`AIAgent` instances are stateless and the same agent instance can be used with multiple `AgentSession` instances. All state is therefore preserved in the `AgentSession`. +An `AgentSession` can both represent chat history plus any other state that the agent needs to preserve across multiple interactions. +The chat history may be stored in the `AgentSession` object itself, or remotely, with the `AgentSession` only containing a reference to the remote chat history. +The `AgentSession` state may also include memories or references to memories stored remotely. > [!TIP] > To learn more about Chat History and Memory in the Agent Framework, see [Agent Chat History and Memory](./agent-memory.md). -### AgentThread Creation +### AgentSession Creation -`AgentThread` instances can be created in two ways: +`AgentSession` instances can be created in two ways: -1. By calling `GetNewThreadAsync` on the agent. -1. By running the agent and not providing an `AgentThread`. In this case the agent will create a throwaway `AgentThread` which will only be used for the duration of the run. +1. By calling `GetNewSessionAsync` on the agent. +1. By running the agent and not providing an `AgentSession`. In this case the agent will create a throwaway `AgentSession` which will only be used for the duration of the run. Some underlying service stored conversations/threads/responses might be persistently created in an underlying service, where the service requires this, for example, Foundry Agents or OpenAI Responses. Any cleanup or deletion of these is the responsibility of the user. ::: zone pivot="programming-language-csharp" ```csharp -// Create a new thread. -AgentThread thread = await agent.GetNewThreadAsync(); -// Run the agent with the thread. -var response = await agent.RunAsync("Hello, how are you?", thread); +// Create a new session. +AgentSession session = await agent.GetNewSessionAsync(); +// Run the agent with the session. +var response = await agent.RunAsync("Hello, how are you?", session); -// Run an agent with a temporary thread. +// Run an agent with a temporary session. response = await agent.RunAsync("Hello, how are you?"); ``` ::: zone-end -### AgentThread Storage +### AgentSession Storage -`AgentThread` instances can be serialized and stored for later use. This allows for the preservation of conversation context across different sessions or service calls. +`AgentSession` instances can be serialized and stored for later use. This allows for the preservation of conversation context across different sessions or service calls. -For cases where the conversation history is stored in a service, the serialized `AgentThread` will contain an +For cases where the conversation history is stored in a service, the serialized `AgentSession` will contain an id that points to the conversation history in the service. -For cases where the conversation history is managed in-memory, the serialized `AgentThread` will contain the messages +For cases where the conversation history is managed in-memory, the serialized `AgentSession` will contain the messages themselves. ::: zone pivot="programming-language-csharp" ```csharp -// Create a new thread. -AgentThread thread = await agent.GetNewThreadAsync(); -// Run the agent with the thread. -var response = await agent.RunAsync("Hello, how are you?", thread); - -// Serialize the thread for storage. -JsonElement serializedThread = thread.Serialize(); -// Deserialize the thread state after loading from storage. -AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread); - -// Run the agent with the resumed thread. -var response = await agent.RunAsync("Hello, how are you?", resumedThread); +// Create a new session. +AgentSession session = await agent.GetNewSessionAsync(); +// Run the agent with the session. +var response = await agent.RunAsync("Hello, how are you?", session); + +// Serialize the session for storage. +JsonElement serializedSession = session.Serialize(); +// Deserialize the session state after loading from storage. +AgentSession resumedSession = await agent.DeserializeSessionAsync(serializedSession); + +// Run the agent with the resumed session. +var response = await agent.RunAsync("Hello, how are you?", resumedSession); ``` -The Microsoft Agent Framework provides built-in support for managing multi-turn conversations with AI agents. This includes maintaining context across multiple interactions. Different agent types and underlying services that are used to build agents might support different threading types, and Agent Framework abstracts these differences away, providing a consistent interface for developers. +The Microsoft Agent Framework provides built-in support for managing multi-turn conversations with AI agents. This includes maintaining context across multiple interactions. Different agent types and underlying services that are used to build agents might support different chat history types, and Agent Framework abstracts these differences away, providing a consistent interface for developers. For example, when using a `ChatAgent` based on a Foundry agent, the conversation history is persisted in the service. While when using a `ChatAgent` based on chat completion with gpt-4, the conversation history is in-memory and managed by the agent. -The differences between the underlying threading models are abstracted away via the `AgentThread` type. +The differences between the underlying chat history models are abstracted away via the `AgentSession` type. -## Agent/AgentThread relationship +## Agent/AgentSession relationship -`AIAgent` instances are stateless and the same agent instance can be used with multiple `AgentThread` instances. +`AIAgent` instances are stateless and the same agent instance can be used with multiple `AgentSession` instances. -Not all agents support all `AgentThread` types though. For example if you are using a `ChatClientAgent` with the responses service, `AgentThread` instances created by this agent, will not work with a `ChatClientAgent` using the Foundry Agent service. -This is because these services both support saving the conversation history in the service, and while the two `AgentThread` instances will have references to each service stored conversation, the id from the responses service cannot be used with the Foundry Agent service, and vice versa. +Not all agents support all `AgentSession` types though. For example if you are using a `ChatClientAgent` with the responses service, `AgentSession` instances created by this agent, will not work with a `ChatClientAgent` using the Foundry Agent service. +This is because these services both support saving the conversation history in the service, and while the two `AgentSession` instances will have references to each service stored conversation, the id from the responses service cannot be used with the Foundry Agent service, and vice versa. -It is therefore considered unsafe to use an `AgentThread` instance that was created by one agent with a different agent instance, unless you are aware of the underlying threading model and its implications. +It is therefore considered unsafe to use an `AgentSession` instance that was created by one agent with a different agent instance, unless you are aware of the underlying chat history model and its implications. -## Conversation history support by service / protocol +## Chat history support by service / protocol -| Service | Conversation History Support | +| Service | Chat History Support | |---------|--------------------| -| Foundry Agents | Service stored persistent conversation history | -| OpenAI Responses | Service stored response chains OR in-memory conversation history | -| OpenAI ChatCompletion | In-memory conversation history | -| OpenAI Assistants | Service stored persistent conversation history | -| A2A | Service stored persistent conversation history | +| Foundry Agents | Service stored persistent chat history | +| OpenAI Responses | Service stored response chains OR in-memory chat history | +| OpenAI ChatCompletion | In-memory chat history | +| OpenAI Assistants | Service stored persistent chat history | +| A2A | Service stored persistent chat history | ::: zone-end diff --git a/agent-framework/user-guide/agents/running-agents.md b/agent-framework/user-guide/agents/running-agents.md index d4ed2ccc..6d6522a9 100644 --- a/agent-framework/user-guide/agents/running-agents.md +++ b/agent-framework/user-guide/agents/running-agents.md @@ -279,4 +279,4 @@ for message in response.messages: ## Next steps > [!div class="nextstepaction"] -> [Multi-Turn Conversations and Threading](./multi-turn-conversation.md) +> [Multi-Turn Conversations](./multi-turn-conversation.md) diff --git a/agent-framework/user-guide/hosting/index.md b/agent-framework/user-guide/hosting/index.md index 0f1b68c9..9eaf62d7 100644 --- a/agent-framework/user-guide/hosting/index.md +++ b/agent-framework/user-guide/hosting/index.md @@ -15,7 +15,7 @@ The Agent Framework provides a comprehensive set of hosting libraries that enabl ## Overview As you may already know from the [AI Agents Overview](../../overview/agent-framework-overview.md#ai-agents), `AIAgent` is the fundamental concept of the Agent Framework. It defines an "LLM wrapper" that processes user inputs, makes decisions, calls tools, and performs additional work to execute actions and generate responses. -However, exposing AI agents from your ASP.NET Core application is not trivial. The Agent Framework hosting libraries solve this by registering AI agents in a dependency injection container, allowing you to resolve and use them in your application services. Additionally, the hosting libraries enable you to manage agent dependencies, such as tools and thread storage, from the same dependency injection container. +However, exposing AI agents from your ASP.NET Core application is not trivial. The Agent Framework hosting libraries solve this by registering AI agents in a dependency injection container, allowing you to resolve and use them in your application services. Additionally, the hosting libraries enable you to manage agent dependencies, such as tools and session storage, from the same dependency injection container. Agents can be hosted alongside your application infrastructure, independent of the protocols they use. Similarly, workflows can be hosted and leverage your application's common infrastructure. @@ -60,10 +60,10 @@ var pirateAgent = builder.AddAIAgent("pirate", instructions: "You are a pirate. .WithAITool(new MyTool()); // MyTool is a custom type derived from `AITool` ``` -You can also configure the thread store (storage for conversation data): +You can also configure the session store (storage for conversation data): ```csharp var pirateAgent = builder.AddAIAgent("pirate", instructions: "You are a pirate. Speak like a pirate") - .WithInMemoryThreadStore(); + .WithInMemorySessionStore(); ``` #### AddWorkflow diff --git a/agent-framework/user-guide/model-context-protocol/using-mcp-with-foundry-agents.md b/agent-framework/user-guide/model-context-protocol/using-mcp-with-foundry-agents.md index 8d2315cb..ac547d83 100644 --- a/agent-framework/user-guide/model-context-protocol/using-mcp-with-foundry-agents.md +++ b/agent-framework/user-guide/model-context-protocol/using-mcp-with-foundry-agents.md @@ -125,10 +125,10 @@ var runOptions = new ChatClientAgentRunOptions() The agent is invoked with a question and executes using the configured MCP tools: ```csharp -AgentThread thread = await agent.GetNewThreadAsync(); +AgentSession session = await agent.GetNewSessionAsync(); var response = await agent.RunAsync( "Please summarize the Azure AI Agent documentation related to MCP Tool calling?", - thread, + session, runOptions); Console.WriteLine(response); ``` diff --git a/agent-framework/user-guide/workflows/as-agents.md b/agent-framework/user-guide/workflows/as-agents.md index 645964cb..4fde2af6 100644 --- a/agent-framework/user-guide/workflows/as-agents.md +++ b/agent-framework/user-guide/workflows/as-agents.md @@ -22,7 +22,7 @@ Sometimes you've built a sophisticated workflow with multiple agents, custom exe - **Unified Interface**: Interact with complex workflows using the same API as simple agents - **API Compatibility**: Integrate workflows with existing systems that support the Agent interface - **Composability**: Use workflow agents as building blocks in larger agent systems or other workflows -- **Thread Management**: Leverage agent threads for conversation state, checkpointing, and resumption +- **Session Management**: Leverage agent sessions for conversation state, checkpointing, and resumption - **Streaming Support**: Get real-time updates as the workflow executes ### How It Works @@ -30,7 +30,7 @@ Sometimes you've built a sophisticated workflow with multiple agents, custom exe When you convert a workflow to an agent: 1. The workflow is validated to ensure its start executor can accept chat messages -2. A thread is created to manage conversation state and checkpoints +2. A session is created to manage conversation state and checkpoints 3. Input messages are routed to the workflow's start executor 4. Workflow events are converted to agent response updates 5. External input requests (from `RequestInfoExecutor`) are surfaced as function calls @@ -75,13 +75,13 @@ AIAgent workflowAgent = workflow.AsAgent( ## Using Workflow Agents -### Creating a Thread +### Creating a Session -Each conversation with a workflow agent requires a thread to manage state: +Each conversation with a workflow agent requires a session to manage state: ```csharp -// Create a new thread for the conversation -AgentThread thread = await workflowAgent.GetNewThreadAsync(); +// Create a new session for the conversation +AgentSession session = await workflowAgent.GetNewSessionAsync(); ``` ### Non-Streaming Execution @@ -94,7 +94,7 @@ var messages = new List new(ChatRole.User, "Write an article about renewable energy trends in 2025") }; -AgentResponse response = await workflowAgent.RunAsync(messages, thread); +AgentResponse response = await workflowAgent.RunAsync(messages, session); foreach (ChatMessage message in response.Messages) { @@ -112,7 +112,7 @@ var messages = new List new(ChatRole.User, "Write an article about renewable energy trends in 2025") }; -await foreach (AgentResponseUpdate update in workflowAgent.RunStreamingAsync(messages, thread)) +await foreach (AgentResponseUpdate update in workflowAgent.RunStreamingAsync(messages, session)) { // Process streaming updates from each agent in the workflow if (!string.IsNullOrEmpty(update.Text)) @@ -127,7 +127,7 @@ await foreach (AgentResponseUpdate update in workflowAgent.RunStreamingAsync(mes When a workflow contains executors that request external input (using `RequestInfoExecutor`), these requests are surfaced as function calls in the agent response: ```csharp -await foreach (AgentResponseUpdate update in workflowAgent.RunStreamingAsync(messages, thread)) +await foreach (AgentResponseUpdate update in workflowAgent.RunStreamingAsync(messages, session)) { // Check for function call requests foreach (AIContent content in update.Contents) @@ -144,21 +144,21 @@ await foreach (AgentResponseUpdate update in workflowAgent.RunStreamingAsync(mes } ``` -## Thread Serialization and Resumption +## Session Serialization and Resumption -Workflow agent threads can be serialized for persistence and resumed later: +Workflow agent sessions can be serialized for persistence and resumed later: ```csharp -// Serialize the thread state -JsonElement serializedThread = thread.Serialize(); +// Serialize the session state +JsonElement serializedSession = session.Serialize(); -// Store serializedThread to your persistence layer... +// Store serializedSession to your persistence layer... -// Later, resume the thread -AgentThread resumedThread = await workflowAgent.DeserializeThreadAsync(serializedThread); +// Later, resume the session +AgentSession resumedSession = await workflowAgent.DeserializeSessionAsync(serializedSession); // Continue the conversation -await foreach (var update in workflowAgent.RunStreamingAsync(newMessages, resumedThread)) +await foreach (var update in workflowAgent.RunStreamingAsync(newMessages, resumedSession)) { Console.Write(update.Text); } diff --git a/agent-framework/user-guide/workflows/orchestrations/group-chat.md b/agent-framework/user-guide/workflows/orchestrations/group-chat.md index eb4dfcdd..a618eb7f 100644 --- a/agent-framework/user-guide/workflows/orchestrations/group-chat.md +++ b/agent-framework/user-guide/workflows/orchestrations/group-chat.md @@ -430,14 +430,14 @@ workflow = ( As mentioned at the beginning of this guide, all agents in a group chat see the full conversation history. -Agents in Agent Framework relies on agent threads ([`AgentThread`](../../agents/multi-turn-conversation.md)) to manage context. In a group chat orchestration, agents **do not** share the same thread instance, but the orchestrator ensures that each agent's thread is synchronized with the complete conversation history before each turn. To achieve this, after each agent's turn, the orchestrator broadcasts the response to all other agents, making sure all participants have the latest context for their next turn. +Agents in Agent Framework relies on agent sessions ([`AgentSession`](../../agents/multi-turn-conversation.md)) to manage context. In a group chat orchestration, agents **do not** share the same session instance, but the orchestrator ensures that each agent's session is synchronized with the complete conversation history before each turn. To achieve this, after each agent's turn, the orchestrator broadcasts the response to all other agents, making sure all participants have the latest context for their next turn.

Group Chat Context Synchronization

> [!TIP] -> Agents do not share the same thread instance because different [agent types](../../agents/agent-types/index.md) may have different implementations of the `AgentThread` abstraction. Sharing the same thread instance could lead to inconsistencies in how each agent processes and maintains context. +> Agents do not share the same session instance because different [agent types](../../agents/agent-types/index.md) may have different implementations of the `AgentSession` abstraction. Sharing the same session instance could lead to inconsistencies in how each agent processes and maintains context. After broadcasting the response, the orchestrator then decide the next speaker and sends a request to the selected agent, which now has the full conversation history to generate its response. diff --git a/agent-framework/user-guide/workflows/orchestrations/handoff.md b/agent-framework/user-guide/workflows/orchestrations/handoff.md index e772fbee..fdbe8237 100644 --- a/agent-framework/user-guide/workflows/orchestrations/handoff.md +++ b/agent-framework/user-guide/workflows/orchestrations/handoff.md @@ -582,7 +582,7 @@ Could you provide photos of the damage to expedite the process? ## Context Synchronization -Agents in Agent Framework relies on agent threads ([`AgentThread`](../../agents/multi-turn-conversation.md)) to manage context. In a Handoff orchestration, agents **do not** share the same thread instance, participants are responsible for ensuring context consistency. To achieve this, participants are designed to broadcast their responses or user inputs received to all others in the workflow whenever they generate a response, making sure all participants have the latest context for their next turn. +Agents in Agent Framework relies on agent sessions ([`AgentSession`](../../agents/multi-turn-conversation.md)) to manage context. In a Handoff orchestration, agents **do not** share the same session instance, participants are responsible for ensuring context consistency. To achieve this, participants are designed to broadcast their responses or user inputs received to all others in the workflow whenever they generate a response, making sure all participants have the latest context for their next turn.

Handoff Context Synchronization @@ -592,7 +592,7 @@ Agents in Agent Framework relies on agent threads ([`AgentThread`](../../agents/ > Tool related contents, including handoff tool calls, are not broadcasted to other agents. Only user and agent messages are synchronized across all participants. > [!TIP] -> Agents do not share the same thread instance because different [agent types](../../agents/agent-types/index.md) may have different implementations of the `AgentThread` abstraction. Sharing the same thread instance could lead to inconsistencies in how each agent processes and maintains context. +> Agents do not share the same session instance because different [agent types](../../agents/agent-types/index.md) may have different implementations of the `AgentSession` abstraction. Sharing the same session instance could lead to inconsistencies in how each agent processes and maintains context. After broadcasting the response, the participant then checks whether it needs to handoff the conversation to another agent. If so, it sends a request to the selected agent to take over the conversation. Otherwise, it requests user input or continues autonomously based on the workflow configuration. diff --git a/agent-framework/user-guide/workflows/using-agents.md b/agent-framework/user-guide/workflows/using-agents.md index 881e530e..bcffa998 100644 --- a/agent-framework/user-guide/workflows/using-agents.md +++ b/agent-framework/user-guide/workflows/using-agents.md @@ -144,7 +144,7 @@ Sometimes you might want to customize how AI agents are integrated into a workfl - The invocation of the agent: streaming or non-streaming - The message types the agent will handle, including custom message types - The life cycle of the agent, including initialization and cleanup -- The usage of agent threads and other resources +- The usage of agent sessions and other resources - Additional events emitted during the agent's execution, including custom events - Integration with other workflow features, such as shared states and requests/responses From 838a78c2cb738b37464a9d762de182c3778c2dbb Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Wed, 28 Jan 2026 08:22:50 -0800 Subject: [PATCH 07/12] update to AIFunction to FunctionTool --- agent-framework/integrations/TOC.yml | 4 ++- .../ag-ui/backend-tool-rendering.md | 32 +++++++++---------- .../integrations/ag-ui/human-in-the-loop.md | 30 ++++++++--------- agent-framework/integrations/ag-ui/index.md | 2 +- .../integrations/ag-ui/state-management.md | 12 +++---- .../integrations/{index.md => overview.md} | 4 ++- .../use-purview-with-agent-framework-sdk.md | 0 .../migration-guide/from-autogen/index.md | 14 ++++---- .../from-semantic-kernel/index.md | 6 ++-- agent-framework/tutorials/TOC.yml | 2 -- .../agents/function-tools-approvals.md | 8 ++--- .../tutorials/agents/function-tools.md | 10 +++--- agent-framework/tutorials/plugins/TOC.yml | 2 -- .../workflows/orchestrations/handoff.md | 12 +++---- .../orchestrations/human-in-the-loop.md | 2 +- 15 files changed, 70 insertions(+), 70 deletions(-) rename agent-framework/integrations/{index.md => overview.md} (94%) rename agent-framework/{tutorials/plugins => integrations}/use-purview-with-agent-framework-sdk.md (100%) delete mode 100644 agent-framework/tutorials/plugins/TOC.yml diff --git a/agent-framework/integrations/TOC.yml b/agent-framework/integrations/TOC.yml index 36a87680..138a1c1d 100644 --- a/agent-framework/integrations/TOC.yml +++ b/agent-framework/integrations/TOC.yml @@ -1,4 +1,6 @@ - name: Overview - href: index.md + href: overview.md - name: AG-UI href: ag-ui/TOC.yml +- name: Purview + href: use-purview-with-agent-framework-sdk.md diff --git a/agent-framework/integrations/ag-ui/backend-tool-rendering.md b/agent-framework/integrations/ag-ui/backend-tool-rendering.md index c8ed9d20..c97ee004 100644 --- a/agent-framework/integrations/ag-ui/backend-tool-rendering.md +++ b/agent-framework/integrations/ag-ui/backend-tool-rendering.md @@ -323,15 +323,15 @@ This approach provides: ### Basic Function Tool -You can turn any Python function into a tool using the `@ai_function` decorator: +You can turn any Python function into a tool using the `@tool` decorator: ```python from typing import Annotated from pydantic import Field -from agent_framework import ai_function +from agent_framework import tool -@ai_function +@tool def get_weather( location: Annotated[str, Field(description="The city")], ) -> str: @@ -342,7 +342,7 @@ def get_weather( ### Key Concepts -- **`@ai_function` decorator**: Marks a function as available to the agent +- **`@tool` decorator**: Marks a function as available to the agent - **Type annotations**: Provide type information for parameters - **`Annotated` and `Field`**: Add descriptions to help the agent understand parameters - **Docstring**: Describes what the function does (helps the agent decide when to use it) @@ -354,10 +354,10 @@ You can provide multiple tools to give the agent more capabilities: ```python from typing import Any -from agent_framework import ai_function +from agent_framework import tool -@ai_function +@tool def get_weather( location: Annotated[str, Field(description="The city.")], ) -> str: @@ -365,7 +365,7 @@ def get_weather( return f"The weather in {location} is sunny with a temperature of 22°C." -@ai_function +@tool def get_forecast( location: Annotated[str, Field(description="The city.")], days: Annotated[int, Field(description="Number of days to forecast")] = 3, @@ -392,7 +392,7 @@ Here's a complete server implementation with function tools: import os from typing import Annotated, Any -from agent_framework import ChatAgent, ai_function +from agent_framework import ChatAgent, tool from agent_framework.azure import AzureOpenAIChatClient from agent_framework_ag_ui import add_agent_framework_fastapi_endpoint from azure.identity import AzureCliCredential @@ -401,7 +401,7 @@ from pydantic import Field # Define function tools -@ai_function +@tool def get_weather( location: Annotated[str, Field(description="The city")], ) -> str: @@ -410,7 +410,7 @@ def get_weather( return f"The weather in {location} is sunny with a temperature of 22°C." -@ai_function +@tool def search_restaurants( location: Annotated[str, Field(description="The city to search in")], cuisine: Annotated[str, Field(description="Type of cuisine")] = "any", @@ -588,7 +588,7 @@ outdoor dining! Handle errors gracefully in your tools: ```python -@ai_function +@tool def get_weather( location: Annotated[str, Field(description="The city.")], ) -> str: @@ -606,7 +606,7 @@ def get_weather( Return structured data when appropriate: ```python -@ai_function +@tool def analyze_sentiment( text: Annotated[str, Field(description="The text to analyze")], ) -> dict[str, Any]: @@ -629,7 +629,7 @@ def analyze_sentiment( Provide clear descriptions to help the agent understand when to use tools: ```python -@ai_function +@tool def book_flight( origin: Annotated[str, Field(description="Departure city and airport code, e.g., 'New York, JFK'")], destination: Annotated[str, Field(description="Arrival city and airport code, e.g., 'London, LHR'")], @@ -651,7 +651,7 @@ def book_flight( For related tools, organize them in a class: ```python -from agent_framework import ai_function +from agent_framework import tool class WeatherTools: @@ -660,7 +660,7 @@ class WeatherTools: def __init__(self, api_key: str): self.api_key = api_key - @ai_function + @tool def get_current_weather( self, location: Annotated[str, Field(description="The city.")], @@ -669,7 +669,7 @@ class WeatherTools: # Use self.api_key to call API return f"Current weather in {location}: Sunny, 22°C" - @ai_function + @tool def get_forecast( self, location: Annotated[str, Field(description="The city.")], diff --git a/agent-framework/integrations/ag-ui/human-in-the-loop.md b/agent-framework/integrations/ag-ui/human-in-the-loop.md index 9cbd109e..23392f73 100644 --- a/agent-framework/integrations/ag-ui/human-in-the-loop.md +++ b/agent-framework/integrations/ag-ui/human-in-the-loop.md @@ -667,15 +667,15 @@ Human-in-the-Loop (HITL) is a pattern where the agent requests user approval bef ## Marking Tools for Approval -To require approval for a tool, use the `approval_mode` parameter in the `@ai_function` decorator: +To require approval for a tool, use the `approval_mode` parameter in the `@tool` decorator: ```python -from agent_framework import ai_function +from agent_framework import tool from typing import Annotated from pydantic import Field -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def send_email( to: Annotated[str, Field(description="Email recipient address")], subject: Annotated[str, Field(description="Email subject line")], @@ -686,7 +686,7 @@ def send_email( return f"Email sent to {to} with subject '{subject}'" -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def delete_file( filepath: Annotated[str, Field(description="Path to the file to delete")], ) -> str: @@ -711,7 +711,7 @@ Here's a complete server implementation with approval-required tools: import os from typing import Annotated -from agent_framework import ChatAgent, ai_function +from agent_framework import ChatAgent, tool from agent_framework.azure import AzureOpenAIChatClient from agent_framework_ag_ui import AgentFrameworkAgent, add_agent_framework_fastapi_endpoint from azure.identity import AzureCliCredential @@ -720,7 +720,7 @@ from pydantic import Field # Tools that require approval -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def transfer_money( from_account: Annotated[str, Field(description="Source account number")], to_account: Annotated[str, Field(description="Destination account number")], @@ -731,7 +731,7 @@ def transfer_money( return f"Transferred {amount} {currency} from {from_account} to {to_account}" -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def cancel_subscription( subscription_id: Annotated[str, Field(description="Subscription identifier")], ) -> str: @@ -740,7 +740,7 @@ def cancel_subscription( # Regular tools (no approval required) -@ai_function +@tool def check_balance( account: Annotated[str, Field(description="Account number")], ) -> str: @@ -1041,7 +1041,7 @@ wrapped_agent = AgentFrameworkAgent( Provide detailed descriptions so users understand what they're approving: ```python -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def delete_database( database_name: Annotated[str, Field(description="Name of the database to permanently delete")], ) -> str: @@ -1061,7 +1061,7 @@ Request approval for individual sensitive actions rather than batching: ```python # Good: Individual approval per transfer -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def transfer_money(...): pass # Avoid: Batching multiple sensitive operations @@ -1073,7 +1073,7 @@ def transfer_money(...): pass Use descriptive parameter names and provide context: ```python -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def purchase_item( item_name: Annotated[str, Field(description="Name of the item to purchase")], quantity: Annotated[int, Field(description="Number of items to purchase")], @@ -1101,17 +1101,17 @@ You can mix tools that require approval with those that don't: ```python # No approval needed for read-only operations -@ai_function +@tool def get_account_balance(...): pass -@ai_function +@tool def list_transactions(...): pass # Approval required for write operations -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def transfer_funds(...): pass -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def close_account(...): pass ``` diff --git a/agent-framework/integrations/ag-ui/index.md b/agent-framework/integrations/ag-ui/index.md index 91e43b6f..449aac78 100644 --- a/agent-framework/integrations/ag-ui/index.md +++ b/agent-framework/integrations/ag-ui/index.md @@ -224,7 +224,7 @@ Understanding how Agent Framework concepts map to AG-UI helps you build effectiv | `agent.run()` | HTTP POST Request | Client sends messages via HTTP | | `agent.run_streaming()` | Server-Sent Events | Streaming responses via SSE | | Agent response updates | AG-UI Events | `TEXT_MESSAGE_CONTENT`, `TOOL_CALL_START`, etc. | -| Function tools (`@ai_function`) | Backend Tools | Executed on server, results streamed to client | +| Function tools (`@tool`) | Backend Tools | Executed on server, results streamed to client | | Tool approval mode | Human-in-the-Loop | Approval requests/responses via protocol | | Conversation history | Thread Management | `threadId` maintains context across requests | diff --git a/agent-framework/integrations/ag-ui/state-management.md b/agent-framework/integrations/ag-ui/state-management.md index 4c26b96c..54fc1143 100644 --- a/agent-framework/integrations/ag-ui/state-management.md +++ b/agent-framework/integrations/ag-ui/state-management.md @@ -408,10 +408,10 @@ This configuration maps the `recipe` state field to the `recipe` argument of the Create a tool function that accepts your Pydantic model: ```python -from agent_framework import ai_function +from agent_framework import tool -@ai_function +@tool def update_recipe(recipe: Recipe) -> str: """Update the recipe with new or modified content. @@ -838,13 +838,13 @@ class TaskStep(BaseModel): estimated_duration: str = "5 min" -@ai_function +@tool def generate_task_steps(steps: list[TaskStep]) -> str: """Generate task steps for a given task.""" return f"Generated {len(steps)} steps." -@ai_function +@tool def update_preferences(preferences: dict[str, Any]) -> str: """Update user preferences.""" return "Preferences updated." @@ -869,7 +869,7 @@ agent_with_multiple_state = AgentFrameworkAgent( When a tool returns complex nested data, use `"*"` to map all tool arguments to state: ```python -@ai_function +@tool def create_document(title: str, content: str, metadata: dict[str, Any]) -> str: """Create a document with title, content, and metadata.""" return "Document created." @@ -909,7 +909,7 @@ Benefits: Always write the complete state, not just deltas: ```python -@ai_function +@tool def update_recipe(recipe: Recipe) -> str: """ You MUST write the complete recipe with ALL fields. diff --git a/agent-framework/integrations/index.md b/agent-framework/integrations/overview.md similarity index 94% rename from agent-framework/integrations/index.md rename to agent-framework/integrations/overview.md index 2e02839e..e09c7438 100644 --- a/agent-framework/integrations/index.md +++ b/agent-framework/integrations/overview.md @@ -19,10 +19,11 @@ Microsoft Agent Framework has integrations with many different services, tools a | ------------------------------------------------------------------ | --------------- | | [AG UI](./ag-ui/index.md) | Preview | | [Agent Framework Dev UI](../user-guide/devui/index.md) | Preview | +| [Purview](./use-purview-with-agent-framework-sdk.md) | Preview | ## Chat History Providers -Microsoft Agent Framework supports many differenta agent types with different chat history storage capabilities. +Microsoft Agent Framework supports many different agent types with different chat history storage capabilities. In some cases agents store chat history in the AI service, while in others Agent Framework manages the storage. To allow chat history storage to be customized when managed by Agent Framework, custom Chat History Providers @@ -65,6 +66,7 @@ Here is a list of existing providers that can be used. | ------------------------------------------------------------------ | --------------- | | [Mem0 Memory Provider](https://github.com/microsoft/agent-framework/blob/main/python/packages/mem0/agent_framework_mem0/_provider.py) | Preview | | [Redis Provider](https://github.com/microsoft/agent-framework/blob/main/python/packages/redis/agent_framework_redis/_provider.py) | Preview | +| [Purview Context Provider](use-purview-with-agent-framework-sdk.md) | Preview | ::: zone-end diff --git a/agent-framework/tutorials/plugins/use-purview-with-agent-framework-sdk.md b/agent-framework/integrations/use-purview-with-agent-framework-sdk.md similarity index 100% rename from agent-framework/tutorials/plugins/use-purview-with-agent-framework-sdk.md rename to agent-framework/integrations/use-purview-with-agent-framework-sdk.md diff --git a/agent-framework/migration-guide/from-autogen/index.md b/agent-framework/migration-guide/from-autogen/index.md index 2afa879d..753d4c9a 100644 --- a/agent-framework/migration-guide/from-autogen/index.md +++ b/agent-framework/migration-guide/from-autogen/index.md @@ -95,7 +95,7 @@ result = await agent.run("Help me with this task") 1. Orchestration style: AutoGen pairs an event-driven core with a high‑level `Team`. Agent Framework centers on a typed, graph‑based `Workflow` that routes data along edges and activates executors when inputs are ready. -2. Tools: AutoGen wraps functions with `FunctionTool`. Agent Framework uses `@ai_function`, infers schemas automatically, and adds hosted tools such as a code interpreter and web search. +2. Tools: AutoGen wraps functions with `FunctionTool`. Agent Framework uses `@tool`, infers schemas automatically, and adds hosted tools such as a code interpreter and web search. 3. Agent behavior: `AssistantAgent` is single‑turn unless you increase `max_tool_iterations`. `ChatAgent` is multi‑turn by default and keeps invoking tools until it can return a final answer. @@ -203,16 +203,16 @@ result = await agent.run(task="What's the weather?") #### Agent Framework ChatAgent ```python -from agent_framework import ChatAgent, ai_function +from agent_framework import ChatAgent, tool from agent_framework.openai import OpenAIChatClient # Create simple tools for the example -@ai_function +@tool def get_weather(location: str) -> str: """Get weather for a location.""" return f"Weather in {location}: sunny" -@ai_function +@tool def get_time() -> str: """Get current time.""" return "Current time: 2:30 PM" @@ -438,14 +438,14 @@ tool = FunctionTool( agent = AssistantAgent(name="assistant", model_client=client, tools=[tool]) ``` -#### Agent Framework @ai_function +#### Agent Framework @tool ```python -from agent_framework import ai_function +from agent_framework import tool from typing import Annotated from pydantic import Field -@ai_function +@tool def get_weather( location: Annotated[str, Field(description="The location to get weather for")] ) -> str: diff --git a/agent-framework/migration-guide/from-semantic-kernel/index.md b/agent-framework/migration-guide/from-semantic-kernel/index.md index 25cade95..97be308e 100644 --- a/agent-framework/migration-guide/from-semantic-kernel/index.md +++ b/agent-framework/migration-guide/from-semantic-kernel/index.md @@ -487,9 +487,9 @@ Finally, you can use the decorator to further customize the name and description ```python from typing import Annotated -from agent_framework import ai_function +from agent_framework import tool -@ai_function(name="weather_tool", description="Retrieves weather information for any location") +@tool(name="weather_tool", description="Retrieves weather information for any location") def get_weather(location: Annotated[str, "The location to get the weather for."]) """Get the weather for a given location.""" return f"The weather in {location} is sunny." @@ -524,7 +524,7 @@ print("Plugin state:", plugin.state) ``` > [!NOTE] -> The functions within the class can also be decorated with `@ai_function` to customize the name and description of the tools. +> The functions within the class can also be decorated with `@tool` to customize the name and description of the tools. This mechanism is also useful for tools that need additional input that cannot be supplied by the LLM, such as connections, secrets, etc. diff --git a/agent-framework/tutorials/TOC.yml b/agent-framework/tutorials/TOC.yml index 48a83a86..e48cfea3 100644 --- a/agent-framework/tutorials/TOC.yml +++ b/agent-framework/tutorials/TOC.yml @@ -2,7 +2,5 @@ href: overview.md - name: Agents href: agents/TOC.yml -- name: Plugins - href: plugins/TOC.yml - name: Workflows href: workflows/TOC.yml diff --git a/agent-framework/tutorials/agents/function-tools-approvals.md b/agent-framework/tutorials/agents/function-tools-approvals.md index d75295d6..7c4dd542 100644 --- a/agent-framework/tutorials/agents/function-tools-approvals.md +++ b/agent-framework/tutorials/agents/function-tools-approvals.md @@ -112,15 +112,15 @@ For prerequisites and installing Python packages, see the [Create and run a simp ## Create the agent with function tools requiring approval When using functions, it's possible to indicate for each function, whether it requires human approval before being executed. -This is done by setting the `approval_mode` parameter to `"always_require"` when using the `@ai_function` decorator. +This is done by setting the `approval_mode` parameter to `"always_require"` when using the `@tool` decorator. Here is an example of a simple function tool that fakes getting the weather for a given location. ```python from typing import Annotated -from agent_framework import ai_function +from agent_framework import tool -@ai_function +@tool def get_weather(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str: """Get the current weather for a given location.""" return f"The weather in {location} is cloudy with a high of 15°C." @@ -129,7 +129,7 @@ def get_weather(location: Annotated[str, "The city and state, e.g. San Francisco To create a function that requires approval, you can use the `approval_mode` parameter: ```python -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_require") def get_weather_detail(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str: """Get detailed weather information for a given location.""" return f"The weather in {location} is cloudy with a high of 15°C, humidity 88%." diff --git a/agent-framework/tutorials/agents/function-tools.md b/agent-framework/tutorials/agents/function-tools.md index 573217c4..b9e6d9de 100644 --- a/agent-framework/tutorials/agents/function-tools.md +++ b/agent-framework/tutorials/agents/function-tools.md @@ -94,21 +94,21 @@ def get_weather( return f"The weather in {location} is cloudy with a high of 15°C." ``` -You can also use the `ai_function` decorator to explicitly specify the function's name and description: +You can also use the `@tool` decorator to explicitly specify the function's name and description: ```python from typing import Annotated from pydantic import Field -from agent_framework import ai_function +from agent_framework import tool -@ai_function(name="weather_tool", description="Retrieves weather information for any location") +@tool(name="weather_tool", description="Retrieves weather information for any location") def get_weather( location: Annotated[str, Field(description="The location to get the weather for.")], ) -> str: return f"The weather in {location} is cloudy with a high of 15°C." ``` -If you don't specify the `name` and `description` parameters in the `ai_function` decorator, the framework will automatically use the function's name and docstring as fallbacks. +If you don't specify the `name` and `description` parameters in the `@tool` decorator, the framework will automatically use the function's name and docstring as fallbacks. When creating the agent, you can now provide the function tool to the agent, by passing it to the `tools` parameter. @@ -169,7 +169,7 @@ agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent( ) ``` -You can also decorate the functions with the same `ai_function` decorator as before. +You can also decorate the functions with the same `@tool` decorator as before. ::: zone-end diff --git a/agent-framework/tutorials/plugins/TOC.yml b/agent-framework/tutorials/plugins/TOC.yml deleted file mode 100644 index b8b000c8..00000000 --- a/agent-framework/tutorials/plugins/TOC.yml +++ /dev/null @@ -1,2 +0,0 @@ -- name: Use Microsoft Purview SDK with Agent Framework - href: use-purview-with-agent-framework-sdk.md \ No newline at end of file diff --git a/agent-framework/user-guide/workflows/orchestrations/handoff.md b/agent-framework/user-guide/workflows/orchestrations/handoff.md index fdbe8237..bc3a5a29 100644 --- a/agent-framework/user-guide/workflows/orchestrations/handoff.md +++ b/agent-framework/user-guide/workflows/orchestrations/handoff.md @@ -159,17 +159,17 @@ math_tutor: I'd be happy to help with calculus integration! Integration is the r ## Define a few tools for demonstration ```python -@ai_function +@tool 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.""" return f"Refund processed successfully for order {order_number}." -@ai_function +@tool def check_order_status(order_number: Annotated[str, "Order number to check status for"]) -> str: """Simulated function to check the status of a given order number.""" return f"Order {order_number} is currently being processed and will ship in 2 business days." -@ai_function +@tool def process_return(order_number: Annotated[str, "Order number to process return for"]) -> str: """Simulated function to process a return for a given order number.""" return f"Return initiated successfully for order {order_number}. You will receive return instructions via email." @@ -398,9 +398,9 @@ Handoff workflows can include agents with tools that require human approval befo ```python from typing import Annotated -from agent_framework import ai_function +from agent_framework import tool -@ai_function(approval_mode="always_require") +@tool(approval_mode="always_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.""" return f"Refund processed successfully for order {order_number}." @@ -617,7 +617,7 @@ After broadcasting the response, the participant then checks whether it needs to - **add_handoff()**: Configures specific handoff relationships between agents - **Context Preservation**: Full conversation history is maintained across all handoffs - **Request/Response Cycle**: Workflow requests user input, processes responses, and continues until termination condition is met -- **Tool Approval**: Use `@ai_function(approval_mode="always_require")` for sensitive operations that need human approval +- **Tool Approval**: Use `@tool(approval_mode="always_require")` for sensitive operations that need human approval - **FunctionApprovalRequestContent**: Emitted when an agent calls a tool requiring approval; use `create_response(approved=...)` to respond - **Checkpointing**: Use `with_checkpointing()` for durable workflows that can pause and resume across process restarts - **Specialized Expertise**: Each agent focuses on their domain while collaborating through handoffs diff --git a/agent-framework/user-guide/workflows/orchestrations/human-in-the-loop.md b/agent-framework/user-guide/workflows/orchestrations/human-in-the-loop.md index f1e63e31..7ab1d5ab 100644 --- a/agent-framework/user-guide/workflows/orchestrations/human-in-the-loop.md +++ b/agent-framework/user-guide/workflows/orchestrations/human-in-the-loop.md @@ -87,7 +87,7 @@ builder = ( ## Function Approval with HITL -When your agents use functions that require human approval (e.g., functions decorated with `@ai_function(approval_mode="always_require")`), the HITL mechanism seamlessly integrates function approval requests into the workflow. +When your agents use functions that require human approval (e.g., functions decorated with `@tool(approval_mode="always_require")`), the HITL mechanism seamlessly integrates function approval requests into the workflow. > [!TIP] > See the [Function Approval](../../../tutorials/agents/function-tools-approvals.md) documentation for more details on function approval. From 4e978e0a31d766f658a5e22bb3ca99ad4a72690f Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:12:09 -0800 Subject: [PATCH 08/12] Renamed Github to GitHub --- .../agent-types/github-copilot-agent.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/agent-framework/user-guide/agents/agent-types/github-copilot-agent.md b/agent-framework/user-guide/agents/agent-types/github-copilot-agent.md index c46a07d5..55bb1c61 100644 --- a/agent-framework/user-guide/agents/agent-types/github-copilot-agent.md +++ b/agent-framework/user-guide/agents/agent-types/github-copilot-agent.md @@ -23,7 +23,7 @@ Microsoft Agent Framework supports creating agents that use the [GitHub Copilot Add the required NuGet packages to your project. ```dotnetcli -dotnet add package Microsoft.Agents.AI.GithubCopilot --prerelease +dotnet add package Microsoft.Agents.AI.GitHub.Copilot --prerelease ``` ## Create a GitHub Copilot Agent @@ -94,7 +94,7 @@ Maintain conversation context across multiple interactions using sessions: await using CopilotClient copilotClient = new(); await copilotClient.StartAsync(); -await using GithubCopilotAgent agent = new( +await using GitHubCopilotAgent agent = new( copilotClient, instructions: "You are a helpful assistant. Keep your answers short."); @@ -208,7 +208,7 @@ Import the required classes from Agent Framework: ```python import asyncio -from agent_framework.github import GithubCopilotAgent, GithubCopilotOptions +from agent_framework.github import GitHubCopilotAgent, GitHubCopilotOptions ``` ## Create a GitHub Copilot Agent @@ -219,7 +219,7 @@ The simplest way to create a GitHub Copilot agent: ```python async def basic_example(): - agent = GithubCopilotAgent( + agent = GitHubCopilotAgent( default_options={"instructions": "You are a helpful assistant."}, ) @@ -234,7 +234,7 @@ You can provide explicit configuration through `default_options`: ```python async def explicit_config_example(): - agent = GithubCopilotAgent( + agent = GitHubCopilotAgent( default_options={ "instructions": "You are a helpful assistant.", "model": "gpt-5", @@ -264,7 +264,7 @@ def get_weather( return f"The weather in {location} is sunny with a high of 25C." async def tools_example(): - agent = GithubCopilotAgent( + agent = GitHubCopilotAgent( default_options={"instructions": "You are a helpful weather agent."}, tools=[get_weather], ) @@ -280,7 +280,7 @@ Get responses as they are generated for better user experience: ```python async def streaming_example(): - agent = GithubCopilotAgent( + agent = GitHubCopilotAgent( default_options={"instructions": "You are a helpful assistant."}, ) @@ -298,7 +298,7 @@ Maintain conversation context across multiple interactions: ```python async def thread_example(): - agent = GithubCopilotAgent( + agent = GitHubCopilotAgent( default_options={"instructions": "You are a helpful assistant."}, ) @@ -333,7 +333,7 @@ def prompt_permission( return PermissionRequestResult(kind="denied-interactively-by-user") async def permissions_example(): - agent = GithubCopilotAgent( + agent = GitHubCopilotAgent( default_options={ "instructions": "You are a helpful assistant that can execute shell commands.", "on_permission_request": prompt_permission, @@ -369,7 +369,7 @@ async def mcp_example(): }, } - agent = GithubCopilotAgent( + agent = GitHubCopilotAgent( default_options={ "instructions": "You are a helpful assistant with access to the filesystem and Microsoft Learn.", "on_permission_request": prompt_permission, From 2e03bc39f67eeb5b2416e5cc708cab217be6f966 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Wed, 28 Jan 2026 12:02:40 -0800 Subject: [PATCH 09/12] Update magentic orchestration doc (#820) * Update magentic orchestration doc * Add important node on group chat custom orchestrator --- .../migration-guide/from-autogen/index.md | 26 ++++--------------- .../workflows/orchestrations/group-chat.md | 13 +++++----- .../workflows/orchestrations/magentic.md | 8 +++--- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/agent-framework/migration-guide/from-autogen/index.md b/agent-framework/migration-guide/from-autogen/index.md index 2afa879d..f3ffd45f 100644 --- a/agent-framework/migration-guide/from-autogen/index.md +++ b/agent-framework/migration-guide/from-autogen/index.md @@ -1226,8 +1226,8 @@ manager_agent = ChatAgent( workflow = ( MagenticBuilder() - .participants(researcher=researcher, coder=coder) - .with_standard_manager( + .participants([researcher, coder]) + .with_manager( agent=manager_agent, max_round_count=20, max_stall_count=3, @@ -1240,19 +1240,7 @@ workflow = ( async def magentic_example(): output: str | None = None async for event in workflow.run_stream("Complex research task"): - if isinstance(event, AgentResponseUpdateEvent): - props = event.data.additional_properties if event.data else None - event_type = props.get("magentic_event_type") if props else None - - if event_type == MAGENTIC_EVENT_TYPE_ORCHESTRATOR: - text = event.data.text if event.data else "" - print(f"[ORCHESTRATOR]: {text}") - elif event_type == MAGENTIC_EVENT_TYPE_AGENT_DELTA: - agent_id = props.get("agent_id", event.executor_id) if props else event.executor_id - if event.data and event.data.text: - print(f"[{agent_id}]: {event.data.text}", end="") - - elif isinstance(event, WorkflowOutputEvent): + if isinstance(event, WorkflowOutputEvent): output_messages = cast(list[ChatMessage], event.data) if output_messages: output = output_messages[-1].text @@ -1296,12 +1284,8 @@ manager_agent = ChatAgent( workflow = ( MagenticBuilder() - .participants( - researcher=researcher_agent, - coder=coder_agent, - analyst=analyst_agent, - ) - .with_standard_manager( + .participants([researcher_agent, coder_agent, analyst_agent]) + .with_manager( agent=manager_agent, max_round_count=15, # Limit total rounds max_stall_count=2, # Trigger stall handling diff --git a/agent-framework/user-guide/workflows/orchestrations/group-chat.md b/agent-framework/user-guide/workflows/orchestrations/group-chat.md index a618eb7f..e1ea06c0 100644 --- a/agent-framework/user-guide/workflows/orchestrations/group-chat.md +++ b/agent-framework/user-guide/workflows/orchestrations/group-chat.md @@ -204,7 +204,7 @@ def round_robin_selector(state: GroupChatState) -> str: # Build the group chat workflow workflow = ( GroupChatBuilder() - .with_select_speaker_func(round_robin_selector) + .with_orchestrator(selection_func=round_robin_selector) .participants([researcher, writer]) # Terminate after 4 turns (researcher → writer → researcher → writer) .with_termination_condition(lambda conversation: len(conversation) >= 4) @@ -235,7 +235,7 @@ Guidelines: # Build group chat with agent-based orchestrator workflow = ( GroupChatBuilder() - .with_agent_orchestrator(orchestrator_agent) + .with_orchestrator(agent=orchestrator_agent) # Set a hard termination condition: stop after 4 assistant messages # The agent orchestrator will intelligently decide when to end before this limit but just in case .with_termination_condition(lambda messages: sum(1 for msg in messages if msg.role == Role.ASSISTANT) >= 4) @@ -337,10 +337,8 @@ Workflow completed. ::: zone pivot="programming-language-python" -- **Flexible Orchestrator Strategies**: Choose between simple selectors, agent-based orchestrators, or custom logic +- **Flexible Orchestrator Strategies**: Choose between simple selectors, agent-based orchestrators, or custom logic by using `with_orchestrator()`. - **GroupChatBuilder**: Creates workflows with configurable speaker selection -- **with_select_speaker_func()**: Define custom Python functions for speaker selection -- **with_agent_orchestrator()**: Use an agent-based orchestrator for intelligent speaker coordination - **GroupChatState**: Provides conversation state for selection decisions - **Iterative Collaboration**: Agents build upon each other's contributions - **Event Streaming**: Process `AgentResponseUpdateEvent` and `WorkflowOutputEvent` in real-time @@ -418,12 +416,15 @@ def smart_selector(state: GroupChatState) -> str: workflow = ( GroupChatBuilder() - .with_select_speaker_func(smart_selector, orchestrator_name="SmartOrchestrator") + .with_orchestrator(selection_func=smart_selector, orchestrator_name="SmartOrchestrator") .participants([researcher, writer]) .build() ) ``` +> [!IMPORTANT] +> When using a custom implementation of `BaseGroupChatOrchestrator` for advanced scenarios, all properties must be set, including `participant_registry`, `max_rounds`, and `termination_condition`. `max_rounds` and `termination_condition` set in the builder will be ignored. + ::: zone-end ## Context Synchronization diff --git a/agent-framework/user-guide/workflows/orchestrations/magentic.md b/agent-framework/user-guide/workflows/orchestrations/magentic.md index f1543007..ce278736 100644 --- a/agent-framework/user-guide/workflows/orchestrations/magentic.md +++ b/agent-framework/user-guide/workflows/orchestrations/magentic.md @@ -77,7 +77,7 @@ manager_agent = ChatAgent( ## Build the Magentic Workflow -Use `MagenticBuilder` to configure the workflow with a standard manager: +Use `MagenticBuilder` to configure the workflow with a standard manager(`StandardMagenticManager`): ```python from agent_framework import MagenticBuilder @@ -85,7 +85,7 @@ from agent_framework import MagenticBuilder workflow = ( MagenticBuilder() .participants([researcher_agent, coder_agent]) - .with_standard_manager( + .with_manager( agent=manager_agent, max_round_count=10, max_stall_count=3, @@ -96,7 +96,7 @@ workflow = ( ``` > [!TIP] -> A standard manager is implemented based on the Magentic-One design, with fixed prompts taken from the original paper. You can customize the manager's behavior by passing in your own prompts to `with_standard_manager()`. To further customize the manager, you can also implement your own manager by sub classing the `MagenticManagerBase` class. +> A standard manager is implemented based on the Magentic-One design, with fixed prompts taken from the original paper. You can customize the manager's behavior by passing in your own prompts to `with_manager()`. To further customize the manager, you can also implement your own manager by sub classing the `MagenticManagerBase` class. ## Run the Workflow with Event Streaming @@ -184,7 +184,7 @@ from agent_framework import ( workflow = ( MagenticBuilder() .participants([researcher_agent, analyst_agent]) - .with_standard_manager( + .with_manager( agent=manager_agent, max_round_count=10, max_stall_count=1, From 3a16c99206eccda66c5d625163572c22e79a4c72 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:50:40 -0800 Subject: [PATCH 10/12] Added redirection URL for Purview --- .openpublishing.redirection.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index 3a681299..3f16b7d9 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -834,6 +834,11 @@ "source_path": "agent-framework/user-guide/agents/agent-observability.md", "redirect_url": "/agent-framework/user-guide/observability", "redirect_document_id": true + }, + { + "source_path": "agent-framework/tutorials/plugins/use-purview-with-agent-framework-sdk.md", + "redirect_url": "/agent-framework/integrations/use-purview-with-agent-framework-sdk", + "redirect_document_id": true } ] } From 097d1f15ad3ce40b4ae4c3dc904f73d34b7880a8 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:17:48 -0800 Subject: [PATCH 11/12] Fixed integrations link --- .openpublishing.redirection.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index 3f16b7d9..bbe0a8c3 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -839,6 +839,11 @@ "source_path": "agent-framework/tutorials/plugins/use-purview-with-agent-framework-sdk.md", "redirect_url": "/agent-framework/integrations/use-purview-with-agent-framework-sdk", "redirect_document_id": true + }, + { + "source_path": "agent-framework/integrations/index.md", + "redirect_url": "/agent-framework/integrations/overview", + "redirect_document_id": true } ] } From d6aa37e51e35a8f80bac830aa5772e0f6a4d0d0a Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:50:08 -0800 Subject: [PATCH 12/12] Added docs for Claude Agent SDK (#841) * Added docs for Claude Agent SDK * Small update * Addressed comments --- .../user-guide/agents/agent-types/TOC.yml | 2 + .../agents/agent-types/claude-agent-sdk.md | 218 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 agent-framework/user-guide/agents/agent-types/claude-agent-sdk.md diff --git a/agent-framework/user-guide/agents/agent-types/TOC.yml b/agent-framework/user-guide/agents/agent-types/TOC.yml index 26348797..8cb6356a 100644 --- a/agent-framework/user-guide/agents/agent-types/TOC.yml +++ b/agent-framework/user-guide/agents/agent-types/TOC.yml @@ -2,6 +2,8 @@ href: index.md - name: Anthropic Agents href: anthropic-agent.md +- name: Claude Agents + href: claude-agent-sdk.md - name: Azure AI Foundry Agents href: azure-ai-foundry-agent.md - name: Azure AI Foundry Models Agents via ChatCompletion diff --git a/agent-framework/user-guide/agents/agent-types/claude-agent-sdk.md b/agent-framework/user-guide/agents/agent-types/claude-agent-sdk.md new file mode 100644 index 00000000..235dd1f3 --- /dev/null +++ b/agent-framework/user-guide/agents/agent-types/claude-agent-sdk.md @@ -0,0 +1,218 @@ +--- +title: Claude Agents +description: Learn how to use Microsoft Agent Framework with the Claude Agent SDK. +zone_pivot_groups: programming-languages +author: dmytrostruk +ms.topic: tutorial +ms.author: dmytrostruk +ms.date: 01/30/2026 +ms.service: agent-framework +--- + +# Claude Agents + +Microsoft Agent Framework supports creating agents that use the [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-python) as their backend. Claude Agent SDK agents provide access to Claude's full agentic capabilities through the Claude Code CLI, including file editing, code execution, and advanced tool workflows. + +> [!IMPORTANT] +> Claude Agent SDK agents require the Claude Code CLI to be installed and authenticated. This is different from the [Anthropic Agent](./anthropic-agent.md) which uses the Anthropic API directly. The Claude Agent SDK provides full agentic capabilities via the CLI. + +::: zone pivot="programming-language-csharp" + +.NET support for Claude Agent SDK is not currently available. Please use Python for Claude Agent SDK agents. + +::: zone-end +::: zone pivot="programming-language-python" + +## Prerequisites + +Install the Microsoft Agent Framework Claude package. + +```bash +pip install agent-framework-claude --pre +``` + +## Configuration + +The agent can be optionally configured using the following environment variables: + +| Variable | Description | +|----------|-------------| +| `CLAUDE_AGENT_CLI_PATH` | Path to the Claude CLI executable | +| `CLAUDE_AGENT_MODEL` | Model to use ("sonnet", "opus", "haiku") | +| `CLAUDE_AGENT_CWD` | Working directory for Claude CLI | +| `CLAUDE_AGENT_PERMISSION_MODE` | Permission mode (default, acceptEdits, plan, bypassPermissions) | +| `CLAUDE_AGENT_MAX_TURNS` | Maximum conversation turns | +| `CLAUDE_AGENT_MAX_BUDGET_USD` | Budget limit in USD | + +## Getting Started + +Import the required classes from Agent Framework: + +```python +import asyncio +from agent_framework_claude import ClaudeAgent +``` + +## Create a Claude Agent + +### Basic Agent Creation + +The simplest way to create a Claude agent using the async context manager: + +```python +async def basic_example(): + async with ClaudeAgent( + instructions="You are a helpful assistant.", + ) as agent: + response = await agent.run("Hello, how can you help me?") + print(response.text) +``` + +### With Explicit Configuration + +You can provide explicit configuration through `default_options`: + +```python +async def explicit_config_example(): + async with ClaudeAgent( + instructions="You are a helpful assistant.", + default_options={ + "model": "sonnet", + "permission_mode": "default", + "max_turns": 10, + }, + ) as agent: + response = await agent.run("What can you do?") + print(response.text) +``` + +## Agent Features + +### Built-in Tools + +Claude Agent SDK provides access to built-in tools by passing tool names as strings. These include file operations, shell commands, and more: + +```python +async def builtin_tools_example(): + async with ClaudeAgent( + instructions="You are a helpful coding assistant.", + tools=["Read", "Write", "Bash", "Glob"], + ) as agent: + response = await agent.run("List all Python files in the current directory") + print(response.text) +``` + +### Function Tools + +Equip your agent with custom functions alongside built-in tools: + +```python +from typing import Annotated +from pydantic import Field + +def get_weather( + location: Annotated[str, Field(description="The location to get the weather for.")], +) -> str: + """Get the weather for a given location.""" + return f"The weather in {location} is sunny with a high of 25C." + +async def tools_example(): + async with ClaudeAgent( + instructions="You are a helpful weather agent.", + tools=[get_weather], + ) as agent: + response = await agent.run("What's the weather like in Seattle?") + print(response.text) +``` + +### Streaming Responses + +Get responses as they are generated for better user experience: + +```python +async def streaming_example(): + async with ClaudeAgent( + instructions="You are a helpful assistant.", + ) as agent: + print("Agent: ", end="", flush=True) + async for chunk in agent.run_stream("Tell me a short story."): + if chunk.text: + print(chunk.text, end="", flush=True) + print() +``` + +### Thread Management + +Maintain conversation context across multiple interactions using threads: + +```python +async def thread_example(): + async with ClaudeAgent( + instructions="You are a helpful assistant. Keep your answers short.", + ) as agent: + thread = agent.get_new_thread() + + # First turn + await agent.run("My name is Alice.", thread=thread) + + # Second turn - agent remembers the context via session resumption + response = await agent.run("What is my name?", thread=thread) + print(response.text) # Should mention "Alice" +``` + +### Permission Modes + +Control how the agent handles permission requests for file operations and command execution: + +```python +async def permission_mode_example(): + async with ClaudeAgent( + instructions="You are a coding assistant that can edit files.", + tools=["Read", "Write", "Bash"], + default_options={ + "permission_mode": "acceptEdits", # Auto-accept file edits + }, + ) as agent: + response = await agent.run("Create a hello.py file that prints 'Hello, World!'") + print(response.text) +``` + +### MCP Servers + +Connect to local (stdio) or remote (HTTP) MCP servers for additional capabilities: + +```python +async def mcp_example(): + async with ClaudeAgent( + instructions="You are a helpful assistant with access to the filesystem and Microsoft Learn.", + default_options={ + "mcp_servers": { + # Local stdio server + "filesystem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "."], + }, + # Remote HTTP server + "microsoft-learn": { + "type": "http", + "url": "https://learn.microsoft.com/api/mcp", + }, + }, + }, + ) as agent: + response = await agent.run("Search Microsoft Learn for 'Azure Functions' and summarize the top result") + print(response.text) +``` + +## Using the Agent + +The agent is a standard `BaseAgent` and supports all standard agent operations. + +For more information on how to run and interact with agents, see the [Agent getting started tutorials](../../../tutorials/overview.md). + +::: zone-end + +## Next steps + +> [!div class="nextstepaction"] +> [Custom Agents](./custom-agent.md)