Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Ce tutoriel montre comment ajouter de la mémoire à un agent en implémentant un AIContextProvider et en l'attachant à l'agent.
Important
Tous les types d’agent ne prennent pas en charge AIContextProvider. Cette étape utilise un ChatClientAgent, qui prend en charge AIContextProvider.
Prerequisites
Pour connaître les prérequis et l’installation des packages NuGet, consultez l’étape Créer et exécuter un agent simple dans ce tutoriel.
Créer un AIContextProvider
AIContextProvider est une classe abstraite dont vous pouvez hériter et qui peut être associée à AgentThread pour un ChatClientAgent.
Il vous permet de :
- Exécutez une logique personnalisée avant et après que l’agent appelle le service d’inférence sous-jacent.
- Fournissez un contexte supplémentaire à l’agent avant d’invoquer le service d’inférence sous-jacent.
- Inspectez tous les messages fournis et produits par l’agent.
Événements de pré-invocation et de post-invocation
La AIContextProvider classe a deux méthodes que vous pouvez remplacer pour exécuter une logique personnalisée avant et après que l’agent appelle le service d’inférence sous-jacent :
-
InvokingAsync- appelée avant que l’agent appelle le service d'inférence sous-jacent. Vous pouvez fournir un contexte supplémentaire à l’agent en retournant unAIContextobjet. Ce contexte sera fusionné avec le contexte existant de l'agent avant l'invocation du service sous-jacent. Il est possible de fournir des instructions, des outils et des messages à ajouter à la demande. -
InvokedAsync- appelée après que l’agent a reçu une réponse du service d’inférence sous-jacent. Vous pouvez inspecter les messages de demande et de réponse et mettre à jour l’état du fournisseur de contexte.
Sérialisation
AIContextProvider les instances sont créées et attachées à un AgentThread lorsque le thread est créé et lorsqu'un thread est repris d'un état sérialisé.
L’instance AIContextProvider peut avoir son propre état qui doit être conservé entre les appels de l’agent. Par exemple, un composant de mémoire qui mémorise des informations sur l’utilisateur peut avoir de la mémoire dans le cadre de son état.
Pour autoriser la persistance des threads, vous devez implémenter la SerializeAsync méthode de la AIContextProvider classe. Vous devez également fournir un constructeur qui accepte un JsonElement paramètre, qui peut être utilisé pour désérialiser l’état lors de la reprise d’un thread.
Exemple d’implémentation AIContextProvider
L’exemple suivant d’un composant de mémoire personnalisé mémorise le nom et l’âge d’un utilisateur et le fournit à l’agent avant chaque appel.
Tout d’abord, créez une classe de modèle pour contenir les mémoires.
internal sealed class UserInfo
{
public string? UserName { get; set; }
public int? UserAge { get; set; }
}
Vous pouvez ensuite implémenter la AIContextProvider méthode pour gérer les souvenirs.
La UserInfoMemory classe ci-dessous contient le comportement suivant :
- Il utilise une
IChatClientoption pour rechercher le nom et l’âge de l’utilisateur dans les messages utilisateur lorsque de nouveaux messages sont ajoutés au thread à la fin de chaque exécution. - Il fournit des souvenirs actuels à l’agent avant chaque appel.
- Si aucun souvenir n’est disponible, il demande à l’agent de demander à l’utilisateur des informations manquantes et de ne pas répondre à des questions tant que les informations ne sont pas fournies.
- Il implémente également la sérialisation pour permettre la persistance des mémoires dans le cadre de l’état du thread.
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
internal sealed class UserInfoMemory : AIContextProvider
{
private readonly IChatClient _chatClient;
public UserInfoMemory(IChatClient chatClient, UserInfo? userInfo = null)
{
this._chatClient = chatClient;
this.UserInfo = userInfo ?? new UserInfo();
}
public UserInfoMemory(IChatClient chatClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null)
{
this._chatClient = chatClient;
this.UserInfo = serializedState.ValueKind == JsonValueKind.Object ?
serializedState.Deserialize<UserInfo>(jsonSerializerOptions)! :
new UserInfo();
}
public UserInfo UserInfo { get; set; }
public override async ValueTask InvokedAsync(
InvokedContext context,
CancellationToken cancellationToken = default)
{
if ((this.UserInfo.UserName is null || this.UserInfo.UserAge is null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
{
var result = await this._chatClient.GetResponseAsync<UserInfo>(
context.RequestMessages,
new ChatOptions()
{
Instructions = "Extract the user's name and age from the message if present. If not present return nulls."
},
cancellationToken: cancellationToken);
this.UserInfo.UserName ??= result.Result.UserName;
this.UserInfo.UserAge ??= result.Result.UserAge;
}
}
public override ValueTask<AIContext> InvokingAsync(
InvokingContext context,
CancellationToken cancellationToken = default)
{
StringBuilder instructions = new();
instructions
.AppendLine(
this.UserInfo.UserName is null ?
"Ask the user for their name and politely decline to answer any questions until they provide it." :
$"The user's name is {this.UserInfo.UserName}.")
.AppendLine(
this.UserInfo.UserAge is null ?
"Ask the user for their age and politely decline to answer any questions until they provide it." :
$"The user's age is {this.UserInfo.UserAge}.");
return new ValueTask<AIContext>(new AIContext
{
Instructions = instructions.ToString()
});
}
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
return JsonSerializer.SerializeToElement(this.UserInfo, jsonSerializerOptions);
}
}
Utilisation de AIContextProvider avec un agent
Pour utiliser l’élément personnalisé AIContextProvider, vous devez fournir un AIContextProviderFactory lors de la création de l’agent. Cette fabrique permet à l’agent de créer une nouvelle instance souhaitée AIContextProvider pour chaque thread.
Lors de la création d’un ChatClientAgent, il est possible de fournir un ChatClientAgentOptions qui permet d’ajouter le AIContextProviderFactory en plus de toutes les autres options d’agent.
using System;
using Azure.AI.OpenAI;
using Azure.Identity;
using OpenAI.Chat;
using OpenAI;
ChatClient chatClient = new AzureOpenAIClient(
new Uri("https://<myresource>.openai.azure.com"),
new AzureCliCredential())
.GetChatClient("gpt-4o-mini");
AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions()
{
Instructions = "You are a friendly assistant. Always address the user by their name.",
AIContextProviderFactory = ctx => new UserInfoMemory(
chatClient.AsIChatClient(),
ctx.SerializedState,
ctx.JsonSerializerOptions)
});
Lors de la création d’un nouveau thread, AIContextProvider sera créé par GetNewThread et attaché au thread. Une fois les mémoires extraites, il est donc possible d’accéder au composant mémoire via la méthode du GetService thread et d’inspecter les mémoires.
// Create a new thread for the conversation.
AgentThread thread = agent.GetNewThread();
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));
// Access the memory component via the thread's GetService method.
var userInfo = thread.GetService<UserInfoMemory>()?.UserInfo;
Console.WriteLine($"MEMORY - User Name: {userInfo?.UserName}");
Console.WriteLine($"MEMORY - User Age: {userInfo?.UserAge}");
Ce tutoriel montre comment ajouter de la mémoire à un agent en implémentant un ContextProvider et en l’attachant à l’agent.
Important
Tous les types d’agent ne prennent pas en charge ContextProvider. Cette étape utilise un ChatAgent, qui prend en charge ContextProvider.
Prerequisites
Pour connaître les prérequis et l’installation des packages, consultez l’étape Créer et exécuter un agent simple dans ce tutoriel.
Créer un ContextProvider
ContextProvider est une classe abstraite dont vous pouvez hériter et qui peut être associée à un AgentThread pour un ChatAgent.
Il vous permet de :
- Exécutez une logique personnalisée avant et après que l’agent appelle le service d’inférence sous-jacent.
- Fournissez un contexte supplémentaire à l’agent avant d’invoquer le service d’inférence sous-jacent.
- Inspectez tous les messages fournis et produits par l’agent.
Événements de pré-invocation et de post-invocation
La ContextProvider classe a deux méthodes que vous pouvez remplacer pour exécuter une logique personnalisée avant et après que l’agent appelle le service d’inférence sous-jacent :
-
invoking- appelée avant que l’agent appelle le service d'inférence sous-jacent. Vous pouvez fournir un contexte supplémentaire à l’agent en retournant unContextobjet. Ce contexte sera fusionné avec le contexte existant de l'agent avant l'invocation du service sous-jacent. Il est possible de fournir des instructions, des outils et des messages à ajouter à la demande. -
invoked- appelée après que l’agent a reçu une réponse du service d’inférence sous-jacent. Vous pouvez inspecter les messages de demande et de réponse et mettre à jour l’état du fournisseur de contexte.
Sérialisation
ContextProvider les instances sont créées et attachées à un AgentThread lorsque le thread est créé et lorsqu'un thread est repris d'un état sérialisé.
L’instance ContextProvider peut avoir son propre état qui doit être conservé entre les appels de l’agent. Par exemple, un composant de mémoire qui mémorise des informations sur l’utilisateur peut avoir de la mémoire dans le cadre de son état.
Pour autoriser la persistance des threads, vous devez implémenter la sérialisation pour la ContextProvider classe. Vous devez également fournir un constructeur qui peut restaurer l’état à partir de données sérialisées lors de la reprise d’un thread.
Exemple d’implémentation de ContextProvider
L’exemple suivant d’un composant de mémoire personnalisé mémorise le nom et l’âge d’un utilisateur et le fournit à l’agent avant chaque appel.
Tout d’abord, créez une classe de modèle pour contenir les mémoires.
from pydantic import BaseModel
class UserInfo(BaseModel):
name: str | None = None
age: int | None = None
Vous pouvez ensuite implémenter la ContextProvider méthode pour gérer les souvenirs.
La UserInfoMemory classe ci-dessous contient le comportement suivant :
- Il utilise un client de conversation pour rechercher le nom et l’âge de l’utilisateur dans les messages utilisateur lorsque de nouveaux messages sont ajoutés au thread à la fin de chaque exécution.
- Il fournit des souvenirs actuels à l’agent avant chaque appel.
- Si aucun souvenir n’est disponible, il demande à l’agent de demander à l’utilisateur des informations manquantes et de ne pas répondre à des questions tant que les informations ne sont pas fournies.
- Il implémente également la sérialisation pour permettre la persistance des mémoires dans le cadre de l’état du thread.
from collections.abc import MutableSequence, Sequence
from typing import Any
from agent_framework import ContextProvider, Context, ChatAgent, ChatClientProtocol, ChatMessage, ChatOptions
class UserInfoMemory(ContextProvider):
def __init__(self, chat_client: ChatClientProtocol, user_info: UserInfo | None = None, **kwargs: Any):
"""Create the memory.
If you pass in kwargs, they will be attempted to be used to create a UserInfo object.
"""
self._chat_client = chat_client
if user_info:
self.user_info = user_info
elif kwargs:
self.user_info = UserInfo.model_validate(kwargs)
else:
self.user_info = UserInfo()
async def invoked(
self,
request_messages: ChatMessage | Sequence[ChatMessage],
response_messages: ChatMessage | Sequence[ChatMessage] | None = None,
invoke_exception: Exception | None = None,
**kwargs: Any,
) -> None:
"""Extract user information from messages after each agent call."""
# Ensure request_messages is a list
messages_list = [request_messages] if isinstance(request_messages, ChatMessage) else list(request_messages)
# Check if we need to extract user info from user messages
user_messages = [msg for msg in messages_list if msg.role.value == "user"]
if (self.user_info.name is None or self.user_info.age is None) and user_messages:
try:
# Use the chat client to extract structured information
result = await self._chat_client.get_response(
messages=messages_list,
chat_options=ChatOptions(
instructions=(
"Extract the user's name and age from the message if present. "
"If not present return nulls."
),
response_format=UserInfo,
),
)
# Update user info with extracted data
if result.value and isinstance(result.value, UserInfo):
if self.user_info.name is None and result.value.name:
self.user_info.name = result.value.name
if self.user_info.age is None and result.value.age:
self.user_info.age = result.value.age
except Exception:
pass # Failed to extract, continue without updating
async def invoking(self, messages: ChatMessage | MutableSequence[ChatMessage], **kwargs: Any) -> Context:
"""Provide user information context before each agent call."""
instructions: list[str] = []
if self.user_info.name is None:
instructions.append(
"Ask the user for their name and politely decline to answer any questions until they provide it."
)
else:
instructions.append(f"The user's name is {self.user_info.name}.")
if self.user_info.age is None:
instructions.append(
"Ask the user for their age and politely decline to answer any questions until they provide it."
)
else:
instructions.append(f"The user's age is {self.user_info.age}.")
# Return context with additional instructions
return Context(instructions=" ".join(instructions))
def serialize(self) -> str:
"""Serialize the user info for thread persistence."""
return self.user_info.model_dump_json()
Utilisation de ContextProvider avec un agent
Pour utiliser la valeur personnalisée ContextProvider, vous devez fournir l’instancié ContextProvider lors de la création de l’agent.
Lors de la création d’un ChatAgent, vous pouvez fournir le paramètre context_providers pour attacher le composant mémoire à l’agent.
import asyncio
from agent_framework import ChatAgent
from agent_framework.azure import AzureAIAgentClient
from azure.identity.aio import AzureCliCredential
async def main():
async with AzureCliCredential() as credential:
chat_client = AzureAIAgentClient(credential=credential)
# Create the memory provider
memory_provider = UserInfoMemory(chat_client)
# Create the agent with memory
async with ChatAgent(
chat_client=chat_client,
instructions="You are a friendly assistant. Always address the user by their name.",
context_providers=memory_provider,
) as agent:
# Create a new thread for the conversation
thread = agent.get_new_thread()
print(await agent.run("Hello, what is the square root of 9?", thread=thread))
print(await agent.run("My name is Ruaidhrí", thread=thread))
print(await agent.run("I am 20 years old", thread=thread))
# Access the memory component via the thread's context_providers attribute and inspect the memories
if thread.context_provider:
user_info_memory = thread.context_provider.providers[0]
if isinstance(user_info_memory, UserInfoMemory):
print()
print(f"MEMORY - User Name: {user_info_memory.user_info.name}")
print(f"MEMORY - User Age: {user_info_memory.user_info.age}")
if __name__ == "__main__":
asyncio.run(main())