Storage

Depolama, konuşma geçmişinin nerede yaşadığını, ne kadar geçmişin yüklendiğini ve oturumların ne kadar güvenilir bir şekilde sürdürülebileceğini denetler.

Yerleşik depolama modları

Agent Framework iki normal depolama modunu destekler:

Çalışma Modu Ne depolanır Tipik kullanım
Yerel oturum durumu AgentSession.state tam sohbet geçmişi (örneğin InMemoryHistoryProvider aracılığıyla) Sunucu tarafı konuşma kalıcılığı gerektirmeyen hizmetler
Hizmet tarafından yönetilen depolama Hizmetteki konuşma durumu; AgentSession.service_session_id ona işaret eder Yerel kalıcı konuşma desteğine sahip hizmetler

Bellek içi sohbet geçmişi depolama

Sağlayıcı sunucu tarafı sohbet geçmişi gerektirmediğinde, Agent Framework geçmişi oturumda yerel olarak tutar ve her çalıştırmada ilgili iletiler gönderir.

AIAgent agent = new OpenAIClient("<your_api_key>")
    .GetChatClient(modelName)
    .AsAIAgent(instructions: "You are a helpful assistant.", name: "Assistant");

AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));

// When in-memory chat history storage is used, it's possible to access the chat history
// that is stored in the session via the provider attached to the agent.
var provider = agent.GetService<InMemoryChatHistoryProvider>();
List<ChatMessage>? messages = provider?.GetMessages(session);
from agent_framework import InMemoryHistoryProvider
from agent_framework.openai import OpenAIChatClient

agent = OpenAIChatClient().as_agent(
    name="StorageAgent",
    instructions="You are a helpful assistant.",
    context_providers=[InMemoryHistoryProvider("memory", load_messages=True)],
)

session = agent.create_session()
await agent.run("Remember that I like Italian food.", session=session)

Go, yerel sohbet geçmişini agent.HistoryProvider aracılığıyla agent.Session içinde depolar. Bir geçmiş sağlayıcısı yapılandırmazsanız, Agent Framework açık bir yerel oturum geçirirken kullanılan varsayılan bir bellek içi sağlayıcı oluşturur. Kararlı bir kaynak kimliği veya özel filtreler istediğinizde birini açıkça yapılandırın.

history := agent.NewInMemoryHistoryProvider(agent.InMemoryHistoryProviderConfig{
    SourceID: "chat_history",
})

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Instructions: "You are a helpful assistant.",
    Config: agent.Config{
        Name:            "StorageAgent",
        HistoryProvider: history,
    },
})

session, err := a.CreateSession(ctx)
if err != nil {
    panic(err)
}

_, err = a.RunText(ctx, "Remember that I like Italian food.", agent.WithSession(session)).Collect()
_, err = a.RunText(ctx, "What kind of food do I like?", agent.WithSession(session)).Collect()

Bellek içi geçmiş boyutunu küçültme

Geçmiş model sınırları için çok büyükse bir azaltıcı uygulayın.

AIAgent agent = new OpenAIClient("<your_api_key>")
    .GetChatClient(modelName)
    .AsAIAgent(new ChatClientAgentOptions
    {
        Name = "Assistant",
        ChatOptions = new() { Instructions = "You are a helpful assistant." },
        ChatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
        {
            ChatReducer = new MessageCountingChatReducer(20)
        })
    });

Bir sonraki isteğe yüklenen geçmiş iletilerini sınırlamak için bir HistoryProvider filtre kullanın. Örneğin, yalnızca en son 20 geçmiş iletisini tutun:

history := agent.NewInMemoryHistoryProvider(agent.InMemoryHistoryProviderConfig{
    SourceID: "chat_history",
    ProvideOutputMessageFilter: func(_ context.Context, messages []*message.Message) ([]*message.Message, error) {
        if len(messages) <= 20 {
            return messages, nil
        }

        return messages[len(messages)-20:], nil
    },
})

Anlamsal veya belirteç farkındalıklı azaltma için, yalnızca mesaj sayılarına güvenmek yerine çalıştırma öncesinde bir yoğunlaştırma stratejisi kullanın.

Uyarı

Azaltıcı yapılandırması bellek içi geçmiş sağlayıcıları için geçerlidir. Hizmet tarafından yönetilen geçmiş için azaltma davranışı sağlayıcıya/hizmete özgüdür.

Hizmet tarafından yönetilen depolama

Hizmet konuşma geçmişini yönettiğinde, oturum bir uzak konuşma tanımlayıcısı depolar.

OpenAI Yanıtları ve Konuşmaları için, resp_* ve conv_* gibi hizmet tarafı kimlikleri opaktır ve varsayılan olarak ilgili API anahtarı veya proje kapsamında geçerlidir. Bu genellikle söz konusu anahtar veya projenin kapsamı zaten bir uygulama, kullanıcı veya kiracı olarak belirlenmişse yeterlidir. Aynı yedekleme anahtarına veya projesine sahip birden çok son kullanıcı için bir aracı barındırıyorsanız, bu kimlikleri güvenilir sunucu tarafı depolama alanında tutun, kendi oturum kimliklerinizden eşleyin ve konuşmayı sürdürmeden önce sahipliği doğrulayın.

AIAgent agent = new OpenAIClient("<your_api_key>")
    .GetOpenAIResponseClient(modelName)
    .AsAIAgent(instructions: "You are a helpful assistant.", name: "Assistant");

AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));

// In this case, since we know we are working with a ChatClientAgent, we can cast
// the AgentSession to a ChatClientAgentSession to retrieve the remote conversation
// identifier.
ChatClientAgentSession typedSession = (ChatClientAgentSession)session;
Console.WriteLine(typedSession.ConversationId);
# Rehydrate when the service already has the conversation state.
session = agent.get_session(service_session_id="<service-conversation-id>")
response = await agent.run("Continue this conversation.", session=session)

Go, sağlayıcıya özgü konuşma tanımlayıcılarını içinde session.ServiceID()depolar. Hizmet tarafından yönetilen geçmişi sürdürmeniz gerektiğinde mevcut bir hizmet konuşma kimliğiyle oturum oluşturun:

session, err := a.CreateSession(ctx, agent.WithServiceID("<service-conversation-id>"))
if err != nil {
    panic(err)
}

_, err = a.RunText(ctx, "Continue this conversation.", agent.WithSession(session)).Collect()

Bir sağlayıcı bir çalıştırma sırasında uzak konuşma tanımlayıcısını oluşturduğunda veya güncelleştirdiğinde oturum güncelleştirilir ve çağrıdan sonra bunu inceleyebilirsiniz:

fmt.Println(session.ServiceID())

Yapılandırılmış yerel geçmiş sağlayıcıları, hizmetin yönettiği oturumlarda göz ardı edilir; böylece hizmet, konuşma geçmişinin kaynağı olmaya devam eder.

Hizmet çağrısı başına yerel geçmiş kalıcılığı

Bir araç çağırma işlemi, tek bir agent.run() tamamlanmadan önce birden fazla model çağrısı gerçekleştirebilir. Varsayılan olarak, yerel geçmiş sağlayıcıları tam çalıştırma sonrasında bir kez kalıcı olarak saklanır. Yerel geçmişin, hizmet tarafından yönetilen konuşmaları daha yakından yansıtmasını istiyorsanız, require_per_service_call_history_persistence=True ayarını yaparak geçmiş sağlayıcılarının her model çağrısının etrafında çalışmasını sağlayabilirsiniz.

from agent_framework import Agent, InMemoryHistoryProvider
from agent_framework.openai import OpenAIChatClient

agent = Agent(
    client=OpenAIChatClient(),
    name="StorageAgent",
    instructions="You are a helpful assistant.",
    context_providers=[InMemoryHistoryProvider("memory", load_messages=True)],
    require_per_service_call_history_persistence=True,
)

Önemli

Bu modu yalnızca çerçeveyle yönetilen yerel geçmiş için kullanın. Çalışma zaten hizmet tarafından yönetilen bir konuşmaya bağlıysa (örneğin session.service_session_id veya options={"conversation_id": ...} aracılığıyla), Agent Framework, iki kalıcılık modelini karıştırmak yerine bir hata oluşturur.

Bu mod, özellikle bir araç çağrısından hemen sonra ara yazılımın sonlandırılabildiği durumlarda kullanışlıdır: model çağrısı başına veri saklama, yerel geçmişi, hizmet tarafından yönetilen bir konuşmanın tutacağı şekilde uyumlu tutar.

Go geçmişi sağlayıcıları bir aracı çağrısının etrafında çalışır. Ayrı bir hizmet başına çağrı kalıcılık anahtarı yoktur; Bir araç döngüsü bir çalıştırma içinde birden çok sağlayıcı çağrısı yaparsa, tam çalıştırmadan sonra yerel geçmişi kalıcı hale getirir veya uygulamanızın depolama gereksinimleri için özel bir sağlayıcı/ara yazılım uygular.

Üçüncü taraf/Özel depolama düzeni

Veritabanı/Redis/blob destekli geçmiş için özel bir geçmiş sağlayıcısı uygulayın.

Temel kılavuz:

  • İletileri oturum kapsamı bir anahtar altında depolayın.
  • Döndürülen geçmişi model bağlam sınırları içinde tutun.
  • Sağlayıcıya özgü tanımlayıcıları oturum durumuna kaydedin.

Temel sınıf, geçmiş sağlayıcıları için Microsoft.Agents.AI.ChatHistoryProvider'dir. Geçmiş sağlayıcıları aracı işlem hattına katılır, aracı giriş iletilerine katkıda bulunabilir veya bu iletileri geçersiz kılabilir ve yeni iletileri depolayabilir. ChatHistoryProvider kendi özel geçmiş sağlayıcınızı uygulamak için geçersiz kılınabilecek çeşitli sanal yöntemlere sahiptir. Daha fazla bilgi için, neyin geçersiz kılınacağını anlamak amacıyla aşağıdaki farklı uygulama seçeneklerine bakın.

ChatHistoryProvider Durum

Bir ChatHistoryProvider örnek bir aracıya eklenir ve tüm oturumlar için aynı örnek kullanılır. Bu, sağlayıcı örneğinde ChatHistoryProvider oturuma özgü bir durum depolamaması gerektiği anlamına gelir. bir ChatHistoryProvider alandaki veritabanı istemcisine başvuru içerebilir, ancak bir alandaki sohbet geçmişi için veritabanı anahtarına sahip olmamalıdır.

Bunun yerine, ChatHistoryProvider veritabanı anahtarları, iletiler veya kendi içinde ilgili başka herhangi bir şey gibi oturuma AgentSession özgü değerleri depolayabilir. ChatHistoryProvider üzerindeki sanal yöntemlerin tümüne, geçerli AIAgent ve AgentSession için bir başvuru geçirilir.

Belirtilmiş türdeki durumu AgentSession içinde kolayca depolamak için bir yardımcı sınıf sağlanır.

// First define a type containing the properties to store in state
internal class MyCustomState
{
    public string? DbKey { get; set; }
}

// Create the helper
var sessionStateHelper = new ProviderSessionState<MyCustomState>(
    // stateInitializer is called when there is no state in the session for this ChatHistoryProvider yet
    stateInitializer: currentSession => new MyCustomState() { DbKey = Guid.NewGuid().ToString() },
    // The key under which to store state in the session for this provider. Make sure it does not clash with the keys of other providers.
    stateKey: this.GetType().Name,
    // An optional jsonSerializerOptions to control the serialization/deserialization of the custom state object
    jsonSerializerOptions: myJsonSerializerOptions);

// Using the helper you can read state:
MyCustomState state = sessionStateHelper.GetOrInitializeState(session);
Console.WriteLine(state.DbKey);

// And write state:
sessionStateHelper.SaveState(session, state);

Basit ChatHistoryProvider uygulama

En basit ChatHistoryProvider uygulama genellikle iki yöntemi geçersiz kılar:

  • ChatHistoryProvider.ProvideChatHistoryAsync - İlgili sohbet geçmişini yükleyin ve yüklenen iletileri döndürin.
  • ChatHistoryProvider.StoreChatHistoryAsync - İstek ve yanıt iletilerini depolayın; bunların tümü yeni olmalıdır.

Burada, sohbet geçmişini doğrudan oturum durumunda depolayan basit ChatHistoryProvider bir örnek verilmiştir.

public sealed class SimpleInMemoryChatHistoryProvider : ChatHistoryProvider
{
    private readonly ProviderSessionState<State> _sessionState;

    public SimpleInMemoryChatHistoryProvider(
        Func<AgentSession?, State>? stateInitializer = null,
        string? stateKey = null)
    {
        this._sessionState = new ProviderSessionState<State>(
            stateInitializer ?? (_ => new State()),
            stateKey ?? this.GetType().Name);
    }

    public override string StateKey => this._sessionState.StateKey;

    protected override ValueTask<IEnumerable<ChatMessage>> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default) =>
        // return all messages in the session state
        new(this._sessionState.GetOrInitializeState(context.Session).Messages);

    protected override ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default)
    {
        var state = this._sessionState.GetOrInitializeState(context.Session);

        // Add both request and response messages to the session state.
        var allNewMessages = context.RequestMessages.Concat(context.ResponseMessages ?? []);
        state.Messages.AddRange(allNewMessages);

        this._sessionState.SaveState(context.Session, state);

        return default;
    }

    public sealed class State
    {
        [JsonPropertyName("messages")]
        public List<ChatMessage> Messages { get; set; } = [];
    }
}

Gelişmiş ChatHistoryProvider uygulama

Daha gelişmiş bir uygulama aşağıdaki yöntemleri geçersiz kılmayı seçebilir:

  • ChatHistoryProvider.InvokingCoreAsync - Aracı LLM'yi çağırmadan önce çağrılır ve istek ileti listesinin değiştirilmesine izin verir.
  • ChatHistoryProvider.InvokedCoreAsync - Aracı LLM'yi çağırdıktan ve tüm istek ve yanıt iletilerine erişime izin verdikten sonra çağrılır.

ChatHistoryProvider, InvokingCoreAsync ve InvokedCoreAsync temel uygulamalarını sağlar.

InvokingCoreAsync Temel uygulama aşağıdakileri yapar:

  • çalıştırma için sohbet geçmişi olarak kullanılması gereken iletileri almak için ProvideChatHistoryAsync çağrılır
  • İletiler Func tarafından döndürüldüğünde, isteğe bağlı bir filtre provideOutputMessageFilterProvideChatHistoryAsync uygulanır. Bu filtre Func oluşturucu aracılığıyla ChatHistoryProvider sağlanabilir.
  • Bu işlem, ProvideChatHistoryAsync tarafından döndürülen filtrelenmiş iletileri, çağıran tarafından aracıya iletilen iletilerle birleştirerek aracı istek iletilerini oluşturur. Sohbet geçmişi aracı giriş iletilerine eklenir.
  • tarafından döndürülen ProvideChatHistoryAsync tüm filtrelenmiş iletileri kaynak bilgileriyle damgalar ve bu iletilerin sohbet geçmişinden geldiğini belirtir.

Temel InvokedCoreAsync aşağıdakileri yapar:

  • çalıştırmanın başarısız olup olmadığını denetler ve başarısız olursa, daha fazla işlem yapmadan döndürür.
  • aracı istek iletilerini, ilk etapta ChatHistoryProvider tarafından üretilenleri değil, yalnızca yeni iletileri depolamak istediğimizden, ChatHistoryProvider tarafından üretilen iletileri hariç tutacak şekilde filtreler. Bu filtrenin oluşturucudaki storeInputMessageFilterChatHistoryProvider parametresi aracılığıyla geçersiz kılınabileceğini unutmayın.
  • Filtrelenmiş istek iletilerini ve tüm yanıt iletilerini depolama için StoreChatHistoryAsync öğesine geçirir.

Bir ChatHistoryProvider uygulamak için bu yöntemleri geçersiz kılmak mümkündür, ancak bunun için uygulayanın temel işlevselliği uygun bir şekilde gerçekleştirmesi gerekir. Burada böyle bir uygulama örneği verilmiştir.

public sealed class AdvancedInMemoryChatHistoryProvider : ChatHistoryProvider
{
    private readonly ProviderSessionState<State> _sessionState;

    public AdvancedInMemoryChatHistoryProvider(
        Func<AgentSession?, State>? stateInitializer = null,
        string? stateKey = null)
    {
        this._sessionState = new ProviderSessionState<State>(
            stateInitializer ?? (_ => new State()),
            stateKey ?? this.GetType().Name);
    }

    public override string StateKey => this._sessionState.StateKey;

    protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
    {
        // Retrieve the chat history from the session state.
        var chatHistory = this._sessionState.GetOrInitializeState(context.Session).Messages;

        // Stamp the messages with this class as the source, so that they can be filtered out later if needed when storing the agent input/output.
        var stampedChatHistory = chatHistory.Select(message => message.WithAgentRequestMessageSource(AgentRequestMessageSourceType.ChatHistory, this.GetType().FullName!));

        // Merge the original input with the chat history to produce a combined agent input.
        return new(stampedChatHistory.Concat(context.RequestMessages));
    }

    protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
    {
        if (context.InvokeException is not null)
        {
            return default;
        }

        // Since we are receiving all messages that were contributed earlier, including those from chat history, we need to filter out the messages that came from chat history
        // so that we don't store message we already have in storage.
        var filteredRequestMessages = context.RequestMessages.Where(m => m.GetAgentRequestMessageSourceType() != AgentRequestMessageSourceType.ChatHistory);

        var state = this._sessionState.GetOrInitializeState(context.Session);

        // Add both request and response messages to the state.
        var allNewMessages = filteredRequestMessages.Concat(context.ResponseMessages ?? []);
        state.Messages.AddRange(allNewMessages);

        this._sessionState.SaveState(context.Session, state);

        return default;
    }

    public sealed class State
    {
        [JsonPropertyName("messages")]
        public List<ChatMessage> Messages { get; set; } = [];
    }
}
  • Python'da yalnızca bir geçmiş sağlayıcısı kullanmalıdır load_messages=True.
from agent_framework.openai import OpenAIChatClient

history = DatabaseHistoryProvider(db_client)
agent = OpenAIChatClient().as_agent(
    name="StorageAgent",
    instructions="You are a helpful assistant.",
    context_providers=[history],
)

session = agent.create_session()
await agent.run("Store this conversation.", session=session)

Go'da veritabanı, Redis, BLOB veya dosya tabanlı geçmiş istediğinizde agent.HistoryProvider öğesini uygulayın. agent.NewHistoryProvider tarafından oluşturulan varsayılan yardımcı, Provide içindeki önceki iletileri yükler ve yeni istek/yanıt iletilerini Store içine kalıcı olarak kaydeder. Sağlayıcı örneğinin oturumlar arasında yeniden kullanılabilmesi için tüm depolama anahtarlarını oturumda tutun.

import (
    "context"
    "fmt"
    "time"

    "github.com/microsoft/agent-framework-go/agent"
    "github.com/microsoft/agent-framework-go/message"
)

type MessageStore interface {
    LoadMessages(context.Context, string) ([]*message.Message, error)
    AppendMessages(context.Context, string, []*message.Message) error
}

func NewDatabaseHistoryProvider(store MessageStore) agent.HistoryProvider {
    const stateKey = "database_history.key"

    historyKey := func(session *agent.Session) string {
        var key string
        if ok, _ := session.Get(stateKey, &key); ok && key != "" {
            return key
        }

        key = fmt.Sprintf("history-%d", time.Now().UnixNano())
        session.Set(stateKey, key)
        return key
    }

    return agent.NewHistoryProvider(agent.HistoryProviderConfig{
        SourceID: "database_history",
        Provide: func(ctx context.Context, invoking agent.InvokingContext) ([]*message.Message, error) {
            session, _ := agent.GetOption(invoking.Options, agent.WithSession)
            if session == nil {
                return nil, nil
            }

            return store.LoadMessages(ctx, historyKey(session))
        },
        Store: func(ctx context.Context, invoked agent.InvokedContext) error {
            session, _ := agent.GetOption(invoked.Options, agent.WithSession)
            if session == nil {
                return nil
            }

            allMessages := make([]*message.Message, 0, len(invoked.RequestMessages)+len(invoked.ResponseMessages))
            allMessages = append(allMessages, invoked.RequestMessages...)
            allMessages = append(allMessages, invoked.ResponseMessages...)

            return store.AppendMessages(ctx, historyKey(session), allMessages)
        },
    })
}

Özel sağlayıcıyı aracıya ekleyin:

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Instructions: "You are a helpful assistant.",
    Config: agent.Config{
        Name:            "StorageAgent",
        HistoryProvider: NewDatabaseHistoryProvider(store),
    },
})

Yapılandırılmış bir yerel HistoryProvider oturumu hizmet tarafından yönetilen bir oturumla birleştirmeyin. Belirli bir oturum için yerel geçmiş depolama alanını veya sağlayıcının uzak konuşma durumunu kullanın.

Yeniden başlatmalar arasında oturumların devamlılığı

Yalnızca ileti metnini değil, tam oturum nesnesini kalıcı hale getirin.

JsonElement serialized = agent.SerializeSession(session);
// Store serialized payload in durable storage.
AgentSession resumed = await agent.DeserializeSessionAsync(serialized);
serialized = session.to_dict()
# Store serialized payload in durable storage.
resumed = AgentSession.from_dict(serialized)

Oturumlar JSON serileştirmesi aracılığıyla kalıcı hale getirilebilir. yalnızca ileti metnini veya geçmiş anahtarını değil, tamamını agent.Sessiondepolayın.

data, err := json.Marshal(session)
if err != nil {
    panic(err)
}
if err := os.WriteFile("session.json", data, 0o644); err != nil {
    panic(err)
}

loaded, err := os.ReadFile("session.json")
if err != nil {
    panic(err)
}

var resumed agent.Session
if err := json.Unmarshal(loaded, &resumed); err != nil {
    panic(err)
}

_, err = a.RunText(ctx, "Continue this conversation.", agent.WithSession(&resumed)).Collect()

Veritabanı destekli depolama için oturumu []byte serileştirin ve tercih ettiğiniz arka uçla depolayın:

data, _ := json.Marshal(session)
db.Set(sessionID, data)

data, _ := db.Get(sessionID)
var resumed agent.Session
_ = json.Unmarshal(data, &resumed)

Tip

Tam bir örnek için üçüncü taraf oturum depolama örneğine bakın.

Önemli

AgentSession bir opak durum nesnesi olarak ele alın ve onu oluşturan aracı/sağlayıcı yapılandırmasıyla geri yükleyin. Serileştirilmiş oturumları ve hizmet tarafı oturum kimliklerini güvenilen uygulama durumu olarak depolayın. Barındırılan veya çok kiracılı uygulamalarda, depolanan her oturumun sürdürülmesine izin vermeden önce onu kimliği doğrulanmış kullanıcıya veya kiracıya bağlayın.

Tip

Birincil geçmiş yüklemesini etkilemeden zenginleştirilmiş bağlam ve giriş/çıkış yakalamak için ek bir denetim/değerlendirme geçmişi sağlayıcısı (load_messages=False, store_context_messages=True) kullanın.

Sonraki Adımlar