Bagikan melalui


Menambahkan Memori ke Agen

Tutorial ini menunjukkan cara menambahkan memori ke agen dengan menerapkan AIContextProvider dan melampirkannya ke agen.

Penting

Tidak semua jenis agen mendukung AIContextProvider. Langkah ini menggunakan ChatClientAgent, yang mendukung AIContextProvider.

Prasyarat

Untuk prasyarat dan menginstal paket NuGet, lihat langkah Membuat dan menjalankan agen sederhana dalam tutorial ini.

Membuat AIContextProvider

AIContextProvider adalah kelas abstrak yang dapat Anda warisi, dan yang dapat dikaitkan dengan AgentSession untuk ChatClientAgent. Ini memungkinkan Anda untuk:

  1. Jalankan logika kustom sebelum dan sesudah agen memanggil layanan inferensi yang mendasarinya.
  2. Berikan konteks tambahan kepada agen sebelum memanggil layanan inferensi yang mendasar.
  3. Tinjau semua pesan yang baik disediakan kepada maupun diproduksi oleh si agen.

Peristiwa pra dan pasca pemanggilan

Kelas AIContextProvider ini memiliki dua metode yang dapat Anda ambil alih untuk menjalankan logika kustom sebelum dan sesudah agen memanggil layanan inferensi yang mendasarinya:

  • InvokingAsync - dipanggil sebelum agen mengaktifkan layanan inferensi dasar. Anda dapat memberikan konteks tambahan kepada agen dengan mengembalikan AIContext objek. Konteks ini akan digabungkan dengan konteks agen yang ada sebelum memanggil layanan dasar. Dimungkinkan untuk memberikan instruksi, alat, dan pesan untuk ditambahkan ke permintaan.
  • InvokedAsync - dipanggil setelah agen menerima respons dari layanan inferensi yang mendasar. Anda dapat memeriksa pesan permintaan dan respons, dan memperbarui status penyedia konteks.

Serialization

AIContextProvider instans dibuat dan dilampirkan ke AgentSession saat sesi dibuat, dan ketika sesi dilanjutkan dari status berseri.

Instans AIContextProvider mungkin memiliki statusnya sendiri yang perlu dipertahankan di antara pemanggilan agen. Misalnya, komponen memori yang mengingat informasi tentang pengguna mungkin memiliki kenangan sebagai bagian dari statusnya.

Untuk memungkinkan sesi yang tetap aktif, Anda perlu menerapkan metode SerializeAsync dari kelas AIContextProvider. Anda juga perlu menyediakan konstruktor yang mengambil JsonElement parameter, yang dapat digunakan untuk mendeserialisasi status saat memulai kembali sesi.

Contoh implementasi AIContextProvider

Contoh komponen memori kustom berikut mengingat nama dan usia pengguna dan menyediakannya kepada agen sebelum setiap pemanggilan.

Pertama, buat kelas model untuk menyimpan kenangan.

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

Kemudian Anda dapat mengimplementasikan AIContextProvider untuk mengelola memori. Kelas UserInfoMemory di bawah ini berisi perilaku berikut:

  1. Ini menggunakan IChatClient untuk mencari nama dan usia pengguna dalam pesan pengguna saat pesan baru ditambahkan ke sesi di akhir setiap eksekusi.
  2. Ini menyediakan memori terkini kepada agen sebelum setiap panggilan.
  3. Jika tidak ada memori yang tersedia, agen menginstruksikan untuk meminta informasi yang hilang kepada pengguna, dan tidak menjawab pertanyaan apa pun sampai informasi disediakan.
  4. Ini juga menerapkan serialisasi untuk memungkinkan bertahannya memori sebagai bagian dari status sesi.
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);
    }
}

Menggunakan AIContextProvider dengan agen

Untuk menggunakan kustom AIContextProvider, Anda perlu memberikan AIContextProviderFactory saat membuat agen. Pabrik ini memungkinkan agen untuk membuat instans baru yang diinginkan AIContextProvider untuk setiap sesi.

Saat membuat ChatClientAgent, Anda dapat menyediakan ChatClientAgentOptions objek yang memungkinkan Anda menyediakan AIContextProviderFactory selain semua opsi agen lainnya.

Pabrik adalah fungsi asinkron yang menerima objek konteks dan token pembatalan.

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.AsAIAgent(new ChatClientAgentOptions()
{
    ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." },
    AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(
        new UserInfoMemory(
            chatClient.AsIChatClient(),
            ctx.SerializedState,
            ctx.JsonSerializerOptions))
});

Saat membuat sesi baru, AIContextProvider akan dibuat oleh GetNewSessionAsync dan dilampirkan ke sesi. Karena itu, setelah memori diekstraksi, dimungkinkan untuk mengakses komponen memori melalui metode sesi GetService dan memeriksa memori.

// 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?", 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 session's GetService method.
var userInfo = session.GetService<UserInfoMemory>()?.UserInfo;
Console.WriteLine($"MEMORY - User Name: {userInfo?.UserName}");
Console.WriteLine($"MEMORY - User Age: {userInfo?.UserAge}");

Tutorial ini menunjukkan cara menambahkan memori ke agen dengan menerapkan ContextProvider dan melampirkannya ke agen.

Penting

Tidak semua jenis agen mendukung ContextProvider. Langkah ini menggunakan ChatAgent, yang mendukung ContextProvider.

Prasyarat

Untuk prasyarat dan penginstalan paket, lihat langkah Buat dan jalankan agen sederhana dalam tutorial ini.

Membuat ContextProvider

ContextProvider adalah kelas abstrak yang dapat Anda warisi, dan yang dapat dikaitkan dengan AgentThread untuk ChatAgent. Ini memungkinkan Anda untuk:

  1. Jalankan logika kustom sebelum dan sesudah agen memanggil layanan inferensi yang mendasarinya.
  2. Berikan konteks tambahan kepada agen sebelum memanggil layanan inferensi yang mendasar.
  3. Tinjau semua pesan yang baik disediakan kepada maupun diproduksi oleh si agen.

Peristiwa pra dan pasca pemanggilan

Kelas ContextProvider ini memiliki dua metode yang dapat Anda ambil alih untuk menjalankan logika kustom sebelum dan sesudah agen memanggil layanan inferensi yang mendasarinya:

  • invoking - dipanggil sebelum agen mengaktifkan layanan inferensi dasar. Anda dapat memberikan konteks tambahan kepada agen dengan mengembalikan Context objek. Konteks ini akan digabungkan dengan konteks agen yang ada sebelum memanggil layanan dasar. Dimungkinkan untuk memberikan instruksi, alat, dan pesan untuk ditambahkan ke permintaan.
  • invoked - dipanggil setelah agen menerima respons dari layanan inferensi yang mendasar. Anda dapat memeriksa pesan permintaan dan respons, dan memperbarui status penyedia konteks.

Serialization

ContextProvider instance dibuat dan dilampirkan ke AgentThread saat utas dibuat, dan ketika utas dilanjutkan dari status ter-serialisasi.

Instans ContextProvider mungkin memiliki statusnya sendiri yang perlu dipertahankan di antara pemanggilan agen. Misalnya, komponen memori yang mengingat informasi tentang pengguna mungkin memiliki kenangan sebagai bagian dari statusnya.

Untuk memungkinkan utas yang berlangsung, Anda perlu menerapkan serialisasi untuk kelas ContextProvider. Anda juga perlu menyediakan konstruktor yang dapat memulihkan status dari data berseri saat lanjutkan utas.

Contoh implementasi ContextProvider

Contoh komponen memori kustom berikut mengingat nama dan usia pengguna dan menyediakannya kepada agen sebelum setiap pemanggilan.

Pertama, buat kelas model untuk menyimpan kenangan.

from pydantic import BaseModel

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

Kemudian Anda dapat mengimplementasikan ContextProvider untuk mengelola memori. Kelas UserInfoMemory di bawah ini berisi perilaku berikut:

  1. Ini menggunakan klien obrolan untuk mencari nama dan usia pengguna dalam pesan pengguna saat pesan baru ditambahkan ke utas di akhir setiap eksekusi.
  2. Ini menyediakan memori terkini kepada agen sebelum setiap panggilan.
  3. Jika tidak ada memori yang tersedia, agen menginstruksikan untuk meminta informasi yang hilang kepada pengguna, dan tidak menjawab pertanyaan apa pun sampai informasi disediakan.
  4. Ini juga mengimplementasikan serialisasi untuk memungkinkan memori bertahan sebagai bagian dari status utas.

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()

Menggunakan ContextProvider dengan agen

Untuk menggunakan kustom ContextProvider, Anda perlu memberikan instansiasi ContextProvider saat membuat agen.

Saat membuat sebuah ChatAgent, Anda dapat memberikan parameter context_providers untuk melampirkan komponen memori pada agen.

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())

Langkah selanjutnya