Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym samouczku pokazano, jak dodać pamięć do agenta, implementując komponent AIContextProvider i integrując go z agentem.
Ważne
Nie wszystkie typy agentów obsługują AIContextProvider. W tym kroku jest używany element ChatClientAgent, który obsługuje AIContextProvider element.
Wymagania wstępne
Aby uzyskać wymagania wstępne i zainstalować pakiety NuGet, zobacz krok Tworzenie i uruchamianie prostego agenta w tym samouczku.
Utwórz komponent AIContextProvider
AIContextProvider jest abstrakcyjną klasą, po której można dziedziczyć, i którą można skojarzyć z AgentThread dla ChatClientAgent.
Umożliwia to:
- Uruchom logikę niestandardową przed wywołaniem przez agenta podstawowej usługi wnioskowania i po nim.
- Podaj dodatkowy kontekst agentowi przed wywołaniem podstawowej usługi wnioskowania.
- Sprawdź wszystkie komunikaty dostarczone i wygenerowane przez agenta.
Zdarzenia przed i po wywołaniu
Klasa AIContextProvider ma dwie metody, które można przesłonić, aby uruchomić logikę niestandardową przed i po wywołaniu podstawowej usługi wnioskowania przez agenta:
-
InvokingAsync- wywoływane przed wywołaniem przez agenta podstawowej usługi wnioskowania. Możesz podać dodatkowy kontekst agentowi, zwracającAIContextobiekt. Ten kontekst zostanie scalony z istniejącym kontekstem agenta przed wywołaniem usługi bazowej. Można podać instrukcje, narzędzia i komunikaty, które mają zostać dodane do żądania. -
InvokedAsync— wywoływana po otrzymaniu przez agenta odpowiedzi z wewnętrznej usługi inferencyjnej. Możesz sprawdzić komunikaty żądania i odpowiedzi oraz zaktualizować stan dostawcy kontekstu.
Serializacja
AIContextProvider wystąpienia są tworzone i dołączane do elementu AgentThread w momencie utworzenia wątku oraz gdy wątek jest wznawiany ze stanu serializowanego.
Instancja AIContextProvider może mieć własny stan, który powinien być zachowywany między wywołaniami agenta. Na przykład składnik pamięci, który zapamiętuje informacje o użytkowniku, może mieć wspomnienia w ramach jego stanu.
Aby umożliwić przechowywanie wątków, należy zaimplementować metodę SerializeAsyncAIContextProvider klasy. Należy również podać konstruktor, który przyjmuje parametr JsonElement, którego można użyć do deserializacji stanu podczas wznawiania wątku.
Przykładowa implementacja AIContextProvider
Poniższy przykład niestandardowego składnika pamięci zapamiętuje nazwę i wiek użytkownika i udostępnia go agentowi przed każdym wywołaniem.
Najpierw utwórz klasę modelu do przechowywania wspomnień.
internal sealed class UserInfo
{
public string? UserName { get; set; }
public int? UserAge { get; set; }
}
Następnie można wdrożyć element AIContextProvider aby zarządzać pamięcią.
Poniższa UserInfoMemory klasa zawiera następujące zachowanie:
- Używa elementu
IChatClient, aby wyszukać nazwę użytkownika i wiek w komunikatach użytkownika po dodaniu nowych komunikatów do wątku na końcu każdego uruchomienia. - Udostępnia on wszystkie bieżące wspomnienia agentowi przed każdym wywołaniem.
- Jeśli nie są dostępne żadne wspomnienia, instruuje agenta, aby poprosił użytkownika o brakujące informacje i nie odpowiadał na żadne pytania do momentu podania tych informacji.
- Implementuje również serializację, aby umożliwić zapisywanie pamięci w ramach stanu wątku.
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);
}
}
Używanie komponentu AIContextProvider z agentem
Aby użyć niestandardowego AIContextProviderelementu , należy podać element AIContextProviderFactory podczas tworzenia agenta. Ta fabryka umożliwia agentowi utworzenie nowej instancji żądanego AIContextProvider dla każdego wątku.
Podczas tworzenia ChatClientAgent obiektu można podać ChatClientAgentOptions obiekt, który umożliwia udostępnianie AIContextProviderFactory elementu oprócz wszystkich innych opcji agenta.
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)
});
Podczas tworzenia nowego wątku AIContextProvider element zostanie utworzony przez GetNewThread i dołączony do wątku. Po wyodrębnieniu pamięci możliwe jest uzyskanie dostępu do składnika pamięci za pośrednictwem metody wątku GetService i sprawdzenie pamięci.
// 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}");
W tym samouczku pokazano, jak dodać pamięć do agenta, implementując ContextProvider moduł i dołączając go do agenta.
Ważne
Nie wszystkie typy agentów obsługują ContextProvider. W tym kroku jest używany element ChatAgent, który obsługuje ContextProvider element.
Wymagania wstępne
Aby uzyskać informacje o wymaganiach wstępnych i instalowanych pakietach, zobacz krok Tworzenie i uruchamianie prostego agenta w tym samouczku.
Utwórz element ContextProvider
ContextProvider jest abstrakcyjną klasą, z którą można dziedziczyć, i którą można skojarzyć z elementem AgentThread dla klasy ChatAgent.
Umożliwia to:
- Uruchom logikę niestandardową przed i po tym, jak agent wywoła podstawową usługę wnioskowania.
- Podaj dodatkowy kontekst agentowi przed wywołaniem podstawowej usługi wnioskowania.
- Sprawdź wszystkie komunikaty dostarczone i wygenerowane przez agenta.
Zdarzenia przed i po wywołaniu
Klasa ContextProvider ma dwie metody, które można przesłonić, aby uruchomić logikę niestandardową przed i po wywołaniu podstawowej usługi wnioskowania przez agenta:
-
invoking- wywoływane przed wywołaniem przez agenta podstawowej usługi wnioskowania. Możesz podać dodatkowy kontekst agentowi, zwracającContextobiekt. Ten kontekst zostanie scalony z istniejącym kontekstem agenta przed wywołaniem usługi bazowej. Można podać instrukcje, narzędzia i komunikaty, które mają zostać dodane do żądania. -
invoked— wywoływana po otrzymaniu przez agenta odpowiedzi z wewnętrznej usługi inferencyjnej. Możesz sprawdzić komunikaty żądania i odpowiedzi oraz zaktualizować stan dostawcy kontekstu.
Serializacja
ContextProvider wystąpienia są tworzone i dołączane do elementu AgentThread w momencie utworzenia wątku oraz gdy wątek jest wznawiany ze stanu serializowanego.
Instancja ContextProvider może mieć własny stan, który powinien być zachowywany między wywołaniami agenta. Na przykład składnik pamięci, który zapamiętuje informacje o użytkowniku, może mieć wspomnienia w ramach jego stanu.
Aby umożliwić utrwalanie wątków, należy zaimplementować serializację dla klasy ContextProvider. Należy również podać konstruktor, który może przywrócić stan z serializowanych danych podczas wznawiania wątku.
Przykładowa implementacja ContextProvider
Poniższy przykład niestandardowego składnika pamięci zapamiętuje nazwę i wiek użytkownika i udostępnia go agentowi przed każdym wywołaniem.
Najpierw utwórz klasę modelu do przechowywania wspomnień.
from pydantic import BaseModel
class UserInfo(BaseModel):
name: str | None = None
age: int | None = None
Następnie można wdrożyć element ContextProvider aby zarządzać pamięcią.
Poniższa UserInfoMemory klasa zawiera następujące zachowanie:
- Używa klienta czatu do wyszukiwania nazwy użytkownika i wieku w wiadomościach użytkownika, gdy nowe wiadomości są dodawane do wątku na końcu każdego uruchomienia.
- Udostępnia on wszystkie bieżące wspomnienia agentowi przed każdym wywołaniem.
- Jeśli nie są dostępne żadne wspomnienia, instruuje agenta, aby poprosił użytkownika o brakujące informacje i nie odpowiadał na żadne pytania do momentu podania tych informacji.
- Implementuje również serializację, aby umożliwić zapisywanie pamięci w ramach stanu wątku.
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()
Używanie komponentu ContextProvider z agentem
Aby użyć niestandardowego elementu ContextProvider, należy dostarczyć zainstancjonowane ContextProvider podczas tworzenia agenta.
Podczas tworzenia ChatAgent można podać context_providers parametr w celu dołączenia składnika pamięci do agenta.
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())