Sdílet prostřednictvím


Přidání paměti do agenta

V tomto kurzu se dozvíte, jak do agenta přidat paměť implementací AIContextProvider a připojením k agentu.

Důležité

Ne všechny typy agentů podporují AIContextProvider. Tento krok používá nástroj ChatClientAgent, který skutečně podporuje AIContextProvider.

Požadavky

Informace o požadavcích a instalaci balíčků NuGet naleznete v kroku Vytvoření a spuštění jednoduchého agenta v tomto kurzu.

Vytvořte AIContextProvider

AIContextProvider je abstraktní třída, ze které můžete dědit, a která může být přidružena k objektu AgentThread for a ChatClientAgent. Umožňuje:

  1. Před a po vyvolání základní služby odvozování spusťte vlastní logiku.
  2. Před vyvolání základní služby odvození poskytněte agentu další kontext.
  3. Zkontrolujte všechny zprávy poskytované agentem a produkované agentem.

Události před a po vyvolání

Třída AIContextProvider má dvě metody, které můžete přepsat tak, aby se spustila vlastní logika před a po vyvolání základní služby odvozování:

  • InvokingAsync – volá se před tím, než agent vyvolá základní inferenční službu. Další kontext pro agenta můžete poskytnout vrácením objektu AIContext. Před vyvoláním základní služby se tento kontext sloučí s existujícím kontextem agenta. Do požadavku je možné zadat pokyny, nástroje a zprávy, které se mají přidat.
  • InvokedAsync – volá se po přijetí odpovědi od základní inference služby, kterou agent využívá. Můžete zkontrolovat zprávy požadavků a odpovědí a aktualizovat stav zprostředkovatele kontextu.

Serialization

AIContextProvider instance jsou vytvořeny a připojeny k AgentThread při vytvoření vlákna a když je vlákno obnoveno ze serializovaného stavu.

Instance AIContextProvider může mít svůj vlastní stav, který je potřeba zachovat mezi vyvoláním agenta. Například součást paměti, která si pamatuje informace o uživateli, může mít v rámci svého stavu vzpomínky.

Pokud chcete povolit trvalé podprocesy, musíte implementovat SerializeAsync metodu AIContextProvider třídy. Musíte také zadat konstruktor, který přebírá JsonElement parametr, který lze použít k deserializaci stavu při obnovení vlákna.

Ukázková implementace AIContextProvider

Následující příklad součásti vlastní paměti si pamatuje jméno a věk uživatele a poskytuje ho agentovi před každým vyvoláním.

Nejprve vytvořte třídu modelu, která bude uchovávat vzpomínky.

internal sealed class UserInfo
{
    public string? UserName { get; set; }
    public int? UserAge { get; set; }
}

Pak můžete implementovat AIContextProvider ke správě paměti. Následující UserInfoMemory třída obsahuje následující chování:

  1. Při přidání nových zpráv do vlákna používá IChatClient ke hledání jména a věku uživatele na konci každého spuštění.
  2. Poskytuje agentovi všechny aktuální vzpomínky před každým vyvoláním.
  3. Pokud nejsou k dispozici žádné vzpomínky, dává agentovi pokyn, aby požádal uživatele o chybějící informace, a neodpovídá na žádné otázky, dokud nebudou informace poskytnuty.
  4. Implementuje také serializaci, která umožňuje zachování paměti jako součást stavu vlákna.
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);
    }
}

Použití AIContextProvider s agentem

Pokud chcete použít vlastní AIContextProvider, musíte zadat AIContextProviderFactory při vytváření agenta. Tato faktorie umožňuje agentu vytvořit novou instanci požadované AIContextProvider pro každé vlákno.

Při vytváření ChatClientAgent je možné předat objekt ChatClientAgentOptions, který umožňuje přidat AIContextProviderFactory k ostatním možnostem 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)
});

Při vytváření nového vlákna bude AIContextProvider vytvořen a připojen k tomuto vláknu pomocí GetNewThread. Jakmile se paměti extrahují, je proto možné získat přístup ke komponentě paměti prostřednictvím metody vlákna GetService a zkontrolovat paměti.

// 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}");

V tomto kurzu se dozvíte, jak do agenta přidat paměť implementací ContextProvider a připojením k agentu.

Důležité

Ne všechny typy agentů podporují ContextProvider. Tento krok používá nástroj ChatAgent, který skutečně podporuje ContextProvider.

Požadavky

Informace o požadavcích a instalaci balíčků najdete v části Vytvoření a spuštění jednoduchého agenta v tomto kurzu.

Vytvoření contextprovideru

ContextProvider je abstraktní třída, ze které můžete dědit, a která může být přidružena k AgentThread pro ChatAgent. Umožňuje:

  1. Před a po vyvolání základní služby odvozování spusťte vlastní logiku.
  2. Před vyvolání základní služby odvození poskytněte agentu další kontext.
  3. Zkontrolujte všechny zprávy poskytované agentem a produkované agentem.

Události před a po vyvolání

Třída ContextProvider má dvě metody, které můžete přepsat tak, aby se spustila vlastní logika před a po vyvolání základní služby odvozování:

  • invoking – volá se před tím, než agent vyvolá základní inferenční službu. Další kontext agenta můžete poskytnout vrácením objektu Context . Před vyvoláním základní služby se tento kontext sloučí s existujícím kontextem agenta. Do požadavku je možné zadat pokyny, nástroje a zprávy, které se mají přidat.
  • invoked – volá se po přijetí odpovědi od základní inference služby, kterou agent využívá. Můžete zkontrolovat zprávy požadavků a odpovědí a aktualizovat stav zprostředkovatele kontextu.

Serialization

ContextProvider instance jsou vytvořeny a připojeny k AgentThread při vytvoření vlákna a když je vlákno obnoveno ze serializovaného stavu.

Instance ContextProvider může mít svůj vlastní stav, který je potřeba zachovat mezi vyvoláním agenta. Například součást paměti, která si pamatuje informace o uživateli, může mít v rámci svého stavu vzpomínky.

Chcete-li povolit trvalá vlákna, je nutné implementovat serializaci třídy ContextProvider . Musíte také poskytnout konstruktor, který může obnovit stav ze serializovaných dat při obnovení vlákna.

Ukázková implementace ContextProvider

Následující příklad součásti vlastní paměti si pamatuje jméno a věk uživatele a poskytuje ho agentovi před každým vyvoláním.

Nejprve vytvořte třídu modelu, která bude uchovávat vzpomínky.

from pydantic import BaseModel

class UserInfo(BaseModel):
    name: str | None = None
    age: int | None = None

Pak můžete implementovat ContextProvider ke správě paměti. Následující UserInfoMemory třída obsahuje následující chování:

  1. Používá chatovacího klienta k vyhledání jména a věku uživatele ve zprávách uživatelů při přidání nových zpráv do vlákna na konci každého spuštění.
  2. Poskytuje agentovi všechny aktuální vzpomínky před každým vyvoláním.
  3. Pokud nejsou k dispozici žádné vzpomínky, dává agentovi pokyn, aby požádal uživatele o chybějící informace, a neodpovídá na žádné otázky, dokud nebudou informace poskytnuty.
  4. Implementuje také serializaci, která umožňuje zachování paměti jako součást stavu vlákna.

from agent_framework import ContextProvider, Context, InvokedContext, InvokingContext, ChatAgent, ChatClientProtocol


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."""
        # Check if we need to extract user info from user messages
        user_messages = [msg for msg in request_messages if hasattr(msg, "role") and 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=request_messages,
                    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:
                    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()

Použití ContextProvideru s agentem

Pokud chcete použít vlastní ContextProvider, musíte při vytváření agenta poskytnout instanci ContextProvider .

Při vytváření ChatAgent můžete zadat context_providers parametr pro připojení součásti paměti k agentu.

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(async_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
            user_info_memory = thread.context_provider.providers[0]
            if user_info_memory:
                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())

Další kroky