التخزين

يتحكم التخزين في مكان محفوظات المحادثات، ومقدار المحفوظات التي يتم تحميلها، وكيف يمكن استئناف جلسات العمل بشكل موثوق.

أوضاع التخزين المضمنة

يدعم إطار عمل العامل وضعي تخزين عاديين:

وضع ما يتم تخزينه الاستخدام النموذجي
حالة جلسة العمل المحلية محفوظات الدردشة الكاملة في AgentSession.state (على سبيل المثال عبر InMemoryHistoryProvider) الخدمات التي لا تتطلب استمرار المحادثة من جانب الخادم
التخزين المدار بواسطة الخدمة حالة المحادثة في الخدمة؛ AgentSession.service_session_id يشير إليه الخدمات التي تدعم المحادثة الثابتة الأصلية

تخزين محفوظات الدردشة في الذاكرة

عندما لا يتطلب الموفر محفوظات دردشة من جانب الخادم، يحتفظ إطار عمل العامل بالمحفوظات محليا في جلسة العمل ويرسل رسائل ذات صلة في كل عملية تشغيل.

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 محفوظات الدردشة المحلية من agent.Session خلال agent.HistoryProvider. إذا لم تقم بتكوين موفر محفوظات، يقوم Agent Framework بإنشاء موفر افتراضي في الذاكرة يتم استخدامه عند تمرير جلسة محلية صريحة. قم بتكوين واحد بشكل صريح عندما تريد معرف مصدر ثابت أو عوامل تصفية مخصصة.

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

تقليل حجم المحفوظات في الذاكرة

إذا كانت المحفوظات كبيرة جدا بالنسبة لحدود النموذج، فطبق مخفضا.

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

استخدم عامل HistoryProvider تصفية للحد من رسائل المحفوظات التي تم تحميلها في الطلب التالي. على سبيل المثال، احتفظ بأحدث 20 رسالة محفوظات فقط:

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
    },
})

للحد الدلالي أو المدرك للرمز المميز، استخدم استراتيجية ضغط قبل التشغيل بدلا من الاعتماد فقط على عدد الرسائل.

Note

ينطبق تكوين المخفض على موفري محفوظات الذاكرة. بالنسبة للمحفوظات المدارة بواسطة الخدمة، يكون سلوك التقليل خاصا بموفر الخدمة/ الخدمة.

التخزين المدار بواسطة الخدمة

عندما تدير الخدمة محفوظات المحادثات، تخزن الجلسة معرف محادثة عن بعد.

بالنسبة إلى استجابات OpenAI والمحادثات، تكون معرفات جانب الخدمة مثل resp_* و conv_* معتمة ونطاقها لمفتاح أو مشروع واجهة برمجة التطبيقات المدعومة بشكل افتراضي. عادة ما يكون هذا كافيا عندما يكون هذا المفتاح أو المشروع محدد النطاق بالفعل لتطبيق أو مستخدم أو مستأجر واحد. إذا كنت تستضيف وكيلا للعديد من المستخدمين النهائيين الذين لديهم نفس مفتاح أو مشروع النسخة الاحتياطية، فاحتفظ بهذه المعرفات في مساحة تخزين موثوق بها من جانب الخادم، وقم بتعيينها من معرفات جلسة العمل الخاصة بك، وتحقق من الملكية قبل استئناف محادثة.

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 معرفات المحادثات الخاصة بالموفر في session.ServiceID(). إنشاء جلسة عمل باستخدام معرف محادثة خدمة موجود عندما تحتاج إلى استئناف المحفوظات المدارة بواسطة الخدمة:

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

عندما يقوم موفر بإنشاء معرف المحادثة البعيدة أو تحديثه أثناء التشغيل، يتم تحديث الجلسة ويمكنك فحصها بعد المكالمة:

fmt.Println(session.ServiceID())

يتم تخطي موفري المحفوظات المحلية المكونين للجلسات المدارة بواسطة الخدمة بحيث تظل الخدمة مصدر محفوظات المحادثات.

استمرار التاريخ المحلي لكل استدعاء خدمة

يمكن أن تقوم عمليات تشغيل استدعاء الأدوات بإجراء مكالمات نموذجية متعددة قبل اكتمال واحد agent.run() . بشكل افتراضي، يستمر موفرو المحفوظات المحليون مرة واحدة بعد التشغيل الكامل. إذا كنت تريد أن تعكس المحفوظات المحلية المحادثات المدارة بواسطة الخدمة بشكل أوثق، فقم بتعيين require_per_service_call_history_persistence=True بحيث يتم تشغيل موفري المحفوظات حول كل مكالمة نموذج بدلا من ذلك.

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

Important

استخدم هذا الوضع فقط للمحفوظات المحلية المدارة بواسطة إطار العمل. إذا كان التشغيل مرتبطا بالفعل بمحادثة مدارة بواسطة الخدمة (على سبيل المثال عبر session.service_session_id أو options={"conversation_id": ...})، فإن إطار عمل العامل يثير خطأ بدلا من خلط نموذجي الاستمرار.

يكون هذا الوضع مفيدا بشكل خاص عندما يمكن إنهاء البرنامج الوسيط مباشرة بعد استدعاء أداة: يؤدي استمرار كل مكالمة نموذج إلى الحفاظ على محاذاة المحفوظات المحلية مع ما ستحتفظ به المحادثة المدارة بواسطة الخدمة.

يتم تشغيل موفري محفوظات Go حول استدعاء عامل. لا يوجد مفتاح استمرار منفصل لكل مكالمة خدمة؛ إذا كانت حلقة الأدوات تجري استدعاءات موفر متعددة داخل تشغيل واحد، فاستمر في المحفوظات المحلية بعد التشغيل الكامل أو قم بتنفيذ موفر مخصص/ برنامج وسيط لاحتياجات التخزين للتطبيق الخاص بك.

نمط تخزين مخصص/تابع لجهة خارجية

بالنسبة إلى قاعدة البيانات/المحفوظات المدعومة من Redis/blob، قم بتنفيذ موفر محفوظات مخصص.

إرشادات رئيسية:

  • تخزين الرسائل ضمن مفتاح نطاق جلسة العمل.
  • احتفظ بالمحفوظات التي تم إرجاعها ضمن حدود سياق النموذج.
  • استمرار المعرفات الخاصة بالموفر في حالة جلسة العمل.

الفئة الأساسية لموفري المحفوظات هي Microsoft.Agents.AI.ChatHistoryProvider. يشارك موفرو المحفوظات في مسار العامل، ولديهم القدرة على المساهمة في رسائل إدخال العامل أو تجاوزها ويمكنهم تخزين رسائل جديدة. ChatHistoryProvider لديه أساليب ظاهرية مختلفة يمكن تجاوزها لتنفيذ موفر المحفوظات المخصص الخاص بك. راجع خيارات التنفيذ المختلفة أدناه لمزيد من المعلومات حول ما يجب تجاوزه.

ChatHistoryProvider الحالة

ChatHistoryProvider يتم إرفاق مثيل بعامل وسيتم استخدام نفس المثيل لجميع جلسات العمل. وهذا يعني أنه يجب عدم تخزين أي حالة محددة ChatHistoryProvider لجلسة العمل في مثيل الموفر. ChatHistoryProvider قد يكون لدى مرجع إلى عميل قاعدة بيانات في حقل، ولكن لا يجب أن يحتوي على مفتاح قاعدة بيانات لمحفوظات الدردشة في حقل.

بدلا من ذلك، يمكن تخزين أي قيم محددة ChatHistoryProvider لجلسة العمل، مثل مفاتيح قاعدة البيانات أو الرسائل أو أي شيء آخر ذي صلة في AgentSession حد ذاته. يتم تمرير كافة الأساليب الظاهرية على ChatHistoryProvider مرجع إلى الحالي AIAgent و AgentSession.

لتمكين تخزين الحالة المصنفة AgentSessionبسهولة في ، يتم توفير فئة أداة مساعدة:

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

تنفيذ بسيط ChatHistoryProvider

عادة ما يتجاوز أبسط ChatHistoryProvider تنفيذ طريقتين:

  • ChatHistoryProvider.ProvideChatHistoryAsync - تحميل محفوظات الدردشة ذات الصلة وإرجاع الرسائل المحملة.
  • ChatHistoryProvider.StoreChatHistoryAsync - طلب المتجر ورسائل الاستجابة، وكلها يجب أن تكون جديدة.

فيما يلي مثال على بسيط ChatHistoryProvider يخزن محفوظات الدردشة مباشرة في حالة جلسة العمل.

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; } = [];
    }
}

التنفيذ المتقدم ChatHistoryProvider

يمكن أن يختار تنفيذ أكثر تقدما تجاوز الطرق التالية:

  • ChatHistoryProvider.InvokingCoreAsync - يتم استدعاؤه قبل أن يستدعي العامل LLM ويسمح بتعديل قائمة رسائل الطلب.
  • ChatHistoryProvider.InvokedCoreAsync - يتم استدعاؤه بعد استدعاء العامل LLM ويسمح بالوصول إلى جميع رسائل الطلب والاستجابة.

ChatHistoryProvider يوفر تطبيقات أساسية ل InvokingCoreAsync و InvokedCoreAsync.

InvokingCoreAsync يقوم التنفيذ الأساسي بالآتي:

  • استدعاءات ProvideChatHistoryAsync للحصول على الرسائل التي يجب استخدامها كمحفوظات دردشة للتشغيل
  • تشغيل عامل تصفية FuncprovideOutputMessageFilter اختياري على الرسائل التي تم إرجاعها بواسطة ProvideChatHistoryAsync. يمكن توفير عامل التصفية Func هذا عبر الدالة ChatHistoryProvider الإنشائية.
  • يدمج الرسائل التي تمت تصفيتها التي تم إرجاعها بواسطة ProvideChatHistoryAsync مع الرسائل التي تم تمريرها إلى العامل بواسطة المتصل، لإنتاج رسائل طلب العامل. يتم إلحاق محفوظات الدردشة برسائل إدخال العامل.
  • ختم جميع الرسائل التي تمت تصفيتها التي تم إرجاعها بواسطة ProvideChatHistoryAsync مع معلومات المصدر، مما يشير إلى أن هذه الرسائل تأتي من محفوظات الدردشة.

تقوم InvokedCoreAsync القاعدة بالآتي:

  • يتحقق من فشل التشغيل وإذا كان الأمر كذلك، يعود دون القيام بأي معالجة أخرى.
  • تصفية رسائل طلب العامل لاستبعاد الرسائل التي تم إنتاجها بواسطة ChatHistoryProvider، لأننا نريد تخزين الرسائل الجديدة فقط وليس تلك التي تم إنتاجها من قبل ChatHistoryProvider في المقام الأول. لاحظ أنه يمكن تجاوز عامل التصفية هذا عبر المعلمة storeInputMessageFilter الموجودة في الدالة ChatHistoryProvider الإنشائية.
  • تمرير رسائل الطلب المصفاة وجميع رسائل الاستجابة إلى StoreChatHistoryAsync للتخزين.

من الممكن تجاوز هذه الأساليب لتنفيذ ChatHistoryProvider، ولكن هذا يتطلب من المنفذ تنفيذ الوظيفة الأساسية نفسها حسب الاقتضاء. وفيما يلي مثال على مثل هذا التنفيذ.

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، يجب أن يستخدم 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، نفذ agent.HistoryProvider عندما تريد قاعدة البيانات أو Redis أو blob أو المحفوظات المدعومة بالملفات. يقوم المساعد الافتراضي الذي تم إنشاؤه بواسطة agent.NewHistoryProvider بتحميل الرسائل السابقة في Provide رسائل الطلب/الاستجابة الجديدة والاحتفاظ بها في Store. احتفظ بأي مفاتيح تخزين في جلسة العمل حتى يمكن إعادة استخدام مثيل الموفر عبر جلسات العمل.

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)
        },
    })
}

إرفاق الموفر المخصص بالعامل:

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

لا تقم بدمج محلي HistoryProvider تم تكوينه مع جلسة عمل مدارة بواسطة الخدمة. استخدم إما تخزين المحفوظات المحلية أو حالة المحادثة البعيدة للموفر لجلسة عمل معينة.

جلسات العمل المستمرة عبر عمليات إعادة التشغيل

استمر في كائن جلسة العمل الكامل، وليس فقط نص الرسالة.

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)

يمكن استمرار جلسات العمل من خلال تسلسل JSON. قم بتخزين نص الرسالة أو مفتاح المحفوظات بأكمله agent.Session، وليس فقط.

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

للتخزين المدعوم بقاعدة البيانات، قم بتسلسل الجلسة وتخزينها []byte مع الواجهة الخلفية المفضلة لديك:

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

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

Tip

راجع نموذج تخزين جلسة عمل الجهة الخارجية للحصول على مثال كامل.

Important

تعامل AgentSession ككائن حالة مبهم واستعادته بنفس تكوين العامل/الموفر الذي أنشأه. تخزين جلسات العمل المتسلسلة وأي معرفات جلسة عمل من جانب الخدمة كحالة تطبيق موثوق بها. في التطبيقات المستضافة أو متعددة المستأجرين، اربط كل جلسة مخزنة بالمستخدم أو المستأجر المصادق عليه قبل السماح باستئنافها.

Tip

استخدم موفر محفوظات تدقيق/تقييم إضافي (load_messages=False، store_context_messages=True) لالتقاط سياق تم إثرائه بالإضافة إلى الإدخال/الإخراج دون التأثير على تحميل المحفوظات الأساسية.

الخطوات التالية