Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este tutorial mostra como adicionar memória a um agente implementando um AIContextProvider e anexando-o ao agente.
Importante
Nem todos os tipos de agentes suportam AIContextProvider. Esta etapa utiliza um ChatClientAgent, que oferece suporte ao AIContextProvider.
Pré-requisitos
Para obter pré-requisitos e instalar pacotes NuGet, consulte a etapa Criar e executar um agente simples neste tutorial.
Criar um AIContextProvider
AIContextProvider é uma classe abstrata da qual se pode herdar e que pode ser associada ao AgentThread de um ChatClientAgent.
Permite-lhe:
- Execute a lógica personalizada antes e depois que o agente invoca o serviço de inferência subjacente.
- Forneça contexto adicional ao agente antes que ele invoque o serviço de inferência subjacente.
- Inspecione todas as mensagens fornecidas e produzidas pelo agente.
Eventos pré e pós invocação
A AIContextProvider classe tem dois métodos que você pode substituir para executar a lógica personalizada antes e depois que o agente invoca o serviço de inferência subjacente:
-
InvokingAsync- chamado antes de o agente invocar o serviço de inferência subjacente. Você pode fornecer contexto adicional ao agente retornando umAIContextobjeto. Esse contexto será mesclado com o contexto existente do agente antes de invocar o serviço subjacente. É possível fornecer instruções, ferramentas e mensagens para adicionar à solicitação. -
InvokedAsync- chamado depois de o agente ter recebido uma resposta do serviço de inferência subjacente. Você pode inspecionar as mensagens de solicitação e resposta e atualizar o estado do provedor de contexto.
Serialização
AIContextProvider As instâncias são criadas e anexadas a um AgentThread quando o thread é criado e quando um thread é retomado de um estado serializado.
A AIContextProvider instância pode ter seu próprio estado que precisa ser persistido entre invocações do agente. Por exemplo, um componente de memória que lembra informações sobre o usuário pode ter memórias como parte de seu estado.
Para permitir threads persistentes, precisa de implementar o SerializeAsync método da classe AIContextProvider. Você também precisa fornecer um construtor que usa um JsonElement parâmetro, que pode ser usado para desserializar o estado ao retomar um thread.
Exemplo de implementação AIContextProvider
O exemplo a seguir de um componente de memória personalizado lembra o nome e a idade de um usuário e os fornece ao agente antes de cada invocação.
Primeiro, crie uma classe modelo para armazenar as memórias.
internal sealed class UserInfo
{
public string? UserName { get; set; }
public int? UserAge { get; set; }
}
Em seguida, você pode implementar o AIContextProvider para gerenciar as memórias.
A UserInfoMemory classe abaixo contém o seguinte comportamento:
- Ele usa um
IChatClientpara procurar o nome e a idade do usuário nas mensagens do usuário quando novas mensagens são adicionadas ao thread no final de cada execução. - Ele fornece todas as memórias atuais ao agente antes de cada invocação.
- Se não houver memórias disponíveis, ele instrui o agente a pedir ao usuário as informações ausentes e a não responder a nenhuma pergunta até que as informações sejam fornecidas.
- Ele também implementa a serialização para permitir a persistência das memórias como parte do estado do 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);
}
}
Usando o AIContextProvider com um agente
Para usar o personalizado AIContextProvider, você precisa fornecer um AIContextProviderFactory ao criar o agente. Esta fábrica permite ao agente criar uma nova instância do AIContextProvider desejado para cada thread.
Ao criar um ChatClientAgent é possível fornecer um ChatClientAgentOptions objeto que permite fornecer o AIContextProviderFactory além de todas as outras opções do agente.
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)
});
Ao criar um novo tópico, o AIContextProvider será criado por GetNewThread e anexado ao tópico. Uma vez que as memórias são extraídas, é possível acessar o componente de memória através do método do GetService thread e inspecionar as memórias.
// 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}");
Este tutorial mostra como adicionar memória a um agente implementando um ContextProvider e anexando-o ao agente.
Importante
Nem todos os tipos de agentes suportam ContextProvider. Esta etapa utiliza um ChatAgent, que oferece suporte ao ContextProvider.
Pré-requisitos
Para obter pré-requisitos e instalar pacotes, consulte a etapa Criar e executar um agente simples neste tutorial.
Criar um ContextProvider
ContextProvider é uma classe abstrata da qual se pode herdar e que pode ser associada a um AgentThread para um ChatAgent.
Permite-lhe:
- Execute a lógica personalizada antes e depois que o agente invoca o serviço de inferência subjacente.
- Forneça contexto adicional ao agente antes que ele invoque o serviço de inferência subjacente.
- Inspecione todas as mensagens fornecidas e produzidas pelo agente.
Eventos pré e pós invocação
A ContextProvider classe tem dois métodos que você pode substituir para executar a lógica personalizada antes e depois que o agente invoca o serviço de inferência subjacente:
-
invoking- chamado antes de o agente invocar o serviço de inferência subjacente. Você pode fornecer contexto adicional ao agente retornando umContextobjeto. Esse contexto será mesclado com o contexto existente do agente antes de invocar o serviço subjacente. É possível fornecer instruções, ferramentas e mensagens para adicionar à solicitação. -
invoked- chamado depois de o agente ter recebido uma resposta do serviço de inferência subjacente. Você pode inspecionar as mensagens de solicitação e resposta e atualizar o estado do provedor de contexto.
Serialização
ContextProvider As instâncias são criadas e anexadas a um AgentThread quando o thread é criado e quando um thread é retomado de um estado serializado.
A ContextProvider instância pode ter seu próprio estado que precisa ser persistido entre invocações do agente. Por exemplo, um componente de memória que lembra informações sobre o usuário pode ter memórias como parte de seu estado.
Para permitir threads persistentes, você precisa implementar a serialização para a ContextProvider classe. Você também precisa fornecer um construtor que possa restaurar o estado de dados serializados ao retomar um thread.
Exemplo de implementação do ContextProvider
O exemplo a seguir de um componente de memória personalizado lembra o nome e a idade de um usuário e os fornece ao agente antes de cada invocação.
Primeiro, crie uma classe modelo para armazenar as memórias.
from pydantic import BaseModel
class UserInfo(BaseModel):
name: str | None = None
age: int | None = None
Em seguida, você pode implementar o ContextProvider para gerenciar as memórias.
A UserInfoMemory classe abaixo contém o seguinte comportamento:
- Ele usa um cliente de bate-papo para procurar o nome e a idade do usuário nas mensagens do usuário quando novas mensagens são adicionadas ao thread no final de cada execução.
- Ele fornece todas as memórias atuais ao agente antes de cada invocação.
- Se não houver memórias disponíveis, ele instrui o agente a pedir ao usuário as informações ausentes e a não responder a nenhuma pergunta até que as informações sejam fornecidas.
- Ele também implementa a serialização para permitir a persistência das memórias como parte do estado do 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()
Usando o ContextProvider com um agente
Para usar o personalizado ContextProvider, você precisa fornecer o instanciado ContextProvider ao criar o agente.
Ao criar um ChatAgent , você pode fornecer o context_providers parâmetro para anexar o componente de memória ao agente.
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())