上下文提供者在每次調用時運行,以便在執行前添加上下文,並在執行後處理數據。
備註
關於可用於代理的預設情境提供者清單,請參見情境提供者整合。
內建模式
建立代理時,透過建構器選項配置提供者。
AIContextProvider 是記憶體/上下文豐富功能的內建擴充點。
AIAgent agent = new OpenAIClient("<your_api_key>")
.GetChatClient(modelName)
.AsAIAgent(new ChatClientAgentOptions()
{
ChatOptions = new() { Instructions = "You are a helpful assistant." },
AIContextProviders = [
new MyCustomMemoryProvider()
],
});
AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Remember my name is Alice.", session));
Tip
關於預先建構 AIContextProvider 的實作清單,請參見 Context provider integrations。
建立代理時的常見模式是透過context_providers=[...]設定提供者。
InMemoryHistoryProvider 是用於本地對話記憶的內建歷史資料提供者。
from agent_framework import Agent, InMemoryHistoryProvider
from agent_framework.openai import OpenAIChatClient
agent = Agent(
client=OpenAIChatClient(),
name="MemoryBot",
instructions="You are a helpful assistant.",
context_providers=[InMemoryHistoryProvider("memory", load_messages=True)],
)
session = agent.create_session()
await agent.run("Remember that I prefer vegetarian food.", session=session)
RawAgent 在特定情況下可能會自動新增具有預設來源識別碼 "in_memory" 的 InMemoryHistoryProvider(),但若要確保具備確定性的本機記憶體行為,請明確新增它。
跨工作階段的檔案支援記憶體
當應由模型決定要儲存及透過 file_memory_* 工具取回哪些內容時,請使用 FileMemoryProvider。 在 Python 中,省略scope會從當前會話 ID 衍生出工作資料夾,因此不同會話不會共享記憶體檔案。 傳入穩定的 scope,例如使用者識別碼,以便在不同工作階段之間共用相同的記憶體檔案,並選擇用於後端儲存的 AgentFileStore 實作。
請將 scope 它視為一個不透明的命名空間鍵,而非檔案系統路徑。 例如,將 tenants/alice 映射到一個編碼的資料夾,而非巢狀目錄。 當你希望資料夾名稱保持可讀時,請使用扁平、標準且小寫的值,並在應用程式中授權外部提供的範圍。
# 1. Create the file store the provider will use to persist memory files.
# Here we use a file-system backed store rooted at a local
# ``agent-file-memory`` folder, but any AgentFileStore implementation can
# be used, e.g. InMemoryAgentFileStore or a custom blob-backed store.
memory_root = Path(__file__).parent / "agent-file-memory"
store = FileSystemAgentFileStore(memory_root)
# 2. Create the FileMemoryProvider over that store.
# The ``scope`` determines the scope and lifetime of the memories:
# - A stable scope, like the per-user one below, gives durable memories
# shared by every session for that user. That is what allows the second
# conversation further down to recall what the user said in the first.
# - Omitting ``scope`` (the default) isolates memories to a single session
# (the working folder is derived from the session id).
# Keep the scope a flat, canonical, lowercase value: it is an opaque key
# mapped onto exactly one folder, not a path that expands into
# subdirectories.
file_memory_provider = FileMemoryProvider(store, scope=f"user-{USER_ID}")
# 3. Attach the provider to the agent so it gets the file_memory_* tools.
agent = Agent(
client=client,
name="TravelAssistant",
instructions=(
"You are a helpful travel assistant. Remember what the user tells you about "
"themselves so that you can give better recommendations later."
),
context_providers=[file_memory_provider],
)
建立代理時,透過 agent.Config.ContextProviders 設定提供者。 上下文提供者會在每次代理執行前注入額外的上下文,並在每次代理執行後保存狀態。
a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
Config: agent.Config{
ContextProviders: []agent.ContextProvider{provider},
},
})
將內容提供者與 Harness Agent 搭配使用
上述手動模式只會附加你所選擇的提供者。 Harness Agent 在建立時會組裝一個有序的供應者集合。 利用每個 SDK 的建構選項來停用或替換預設值,並附加額外的提供者。
HarnessAgent預設情況下啟用 TodoProvider、 AgentModeProvider、 FileMemoryProviderAgentSkillsProvider 。 它會在那些內建提供者之後附加來自 HarnessAgentOptions.AIContextProviders 的提供者。
HarnessAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
AIContextProviders = [new MyCustomMemoryProvider()],
DisableAgentSkillsProvider = true,
});
使用 DisableTodoProvider、DisableAgentModeProvider、DisableFileMemory 和 DisableAgentSkillsProvider 來移除預設值。 使用 AgentModeProviderOptions 和 AgentSkillsSource 設定模式與技能;將檔案記憶體儲存體替換為 FileMemoryStore。 檔案存取需透過 FileAccessStore 和 FileAccessProviderOptions 明確啟用,而背景委派則需透過 BackgroundAgents 和 BackgroundAgentsProviderOptions 明確啟用。
AsHarnessAgent(options)和new HarnessAgent(chatClient, options)都接受相同的 HarnessAgentOptions。
create_harness_agent 會依序排列歷程記錄提供者,接著在啟用時進行執行後壓縮,然後是待辦事項、模式以及檔案記憶體提供者。 檔案記憶體預設開啟;技能、檔案存取權限、背景代理及 shell 上下文皆為自願加入。 透過 context_providers= 傳遞的提供者會在最後附加。
agent = create_harness_agent(
client,
context_providers=[UserPreferenceProvider()],
disable_mode=True,
skills_paths=["./skills"],
)
使用 history_provider、 todo_provider、 mode_provider 來替換這些預設值,分別為 disable_todo、 disable_mode, disable_file_memory 作為退出選項。 用 file_memory_store 來替換預設 {cwd}/agent-file-memory 商店。 啟用可選的提供者,包含 file_access_store、 skills_providerskills_pathsbackground_agentsshell_executor;其相關的設定參數可配置權限、指令及環境行為。
Harness Agent 目前還沒有在 Go SDK 裡提供。 透過 agent.Config.ContextProviders 明確新增內容提供者。
自訂上下文提供者
當你需要注入動態指令/訊息/工具或執行後擷取狀態時,使用自訂上下文提供者。
上下文提供者的基底類別是 Microsoft.Agents.AI.AIContextProvider。
情境提供者參與代理管線,能貢獻或覆寫代理輸入訊息,並能從新訊息中擷取資訊。
AIContextProvider 有多種虛擬方法,可以覆寫以實作您自己的自訂內容提供者。
請參閱下方不同的實作選項,了解有關哪些項目要覆寫的詳細資訊。
AIContextProvider 狀態
一個 AIContextProvider 實例會附加到代理,所有會話都會使用同一個實例。
這表示 在 AIContextProvider 提供者實例中不應儲存任何特定會話狀態。
欄位 AIContextProvider 中可能有記憶體服務客戶端的參考,但不應該有欄位中特定記憶體集合的 ID。
相反的,AIContextProvider 可以儲存任何會話特定的值,例如記憶體 ID、訊息,或者任何與 AgentSession 本身相關的資料。
AIContextProvider上的所有虛擬方法都會被傳遞至目前 AIAgent 與 AgentSession 的參考。
為了方便地在 AgentSession 中儲存類型化狀態,提供了一個實用類別。
// First define a type containing the properties to store in state
internal class MyCustomState
{
public string? MemoryId { get; set; }
}
// Create the helper
var sessionStateHelper = new ProviderSessionState<MyCustomState>(
// stateInitializer is called when there is no state in the session for this AIContextProvider yet
stateInitializer: currentSession => new MyCustomState() { MemoryId = 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.MemoryId);
// And write state:
sessionStateHelper.SaveState(session, state);
簡單 AIContextProvider 實作
最簡單的 AIContextProvider 實作通常會覆寫兩種方法:
- AIContextProvider.ProvideAIContextAsync - 載入相關資料並回傳額外指令、訊息或工具。
- AIContextProvider.StoreAIContextAsync - 從新訊息中擷取相關資料並儲存。
這是一個整合記憶體服務的簡單範例 AIContextProvider 。
internal sealed class SimpleServiceMemoryProvider : AIContextProvider
{
private readonly ProviderSessionState<State> _sessionState;
private readonly ServiceClient _client;
public SimpleServiceMemoryProvider(ServiceClient client, Func<AgentSession?, State>? stateInitializer = null)
: base(null, null)
{
this._sessionState = new ProviderSessionState<State>(
stateInitializer ?? (_ => new State()),
this.GetType().Name);
this._client = client;
}
public override string StateKey => this._sessionState.StateKey;
protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
var state = this._sessionState.GetOrInitializeState(context.Session);
if (state.MemoriesId == null)
{
// No stored memories yet.
return new ValueTask<AIContext>(new AIContext());
}
// Find memories that match the current user input.
var memories = this._client.LoadMemories(state.MemoriesId, string.Join("\n", context.AIContext.Messages?.Select(x => x.Text) ?? []));
// Return a new message that contains the text from any memories that were found.
return new ValueTask<AIContext>(new AIContext
{
Messages = [new ChatMessage(ChatRole.User, "Here are some memories to help answer the user question: " + string.Join("\n", memories.Select(x => x.Text)))]
});
}
protected override async ValueTask StoreAIContextAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
var state = this._sessionState.GetOrInitializeState(context.Session);
// Create a memory container in the service for this session
// and save the returned id in the session.
state.MemoriesId ??= this._client.CreateMemoryContainer();
this._sessionState.SaveState(context.Session, state);
// Use the service to extract memories from the user input and agent response.
await this._client.StoreMemoriesAsync(state.MemoriesId, context.RequestMessages.Concat(context.ResponseMessages ?? []), cancellationToken);
}
public class State
{
public string? MemoriesId { get; set; }
}
}
進階 AIContextProvider 實作
更進階的實作可以選擇覆寫下列方法:
- AIContextProvider.InvokingCoreAsync - 在代理呼叫 LLM 前呼叫,允許修改請求訊息清單、工具與指令。
- AIContextProvider.InvokedCoreAsync - 代理程式呼叫 LLM 後呼叫,允許存取所有請求與回應訊息。
AIContextProvider提供InvokingCoreAsync和InvokedCoreAsync的基礎實作。
InvokingCoreAsync基礎實作的做法如下:
- 它會過濾輸入訊息清單,只篩選出呼叫者傳送到代理的訊息。 請注意,此篩選可以透過
AIContextProvider建構函式上的provideInputMessageFilter參數來覆寫。 -
ProvideAIContextAsync以篩選過的請求訊息、現有的工具和指示來進行呼叫。 - 會將所有由
ProvideAIContextAsync返回的訊息加上來源資訊,表示這些訊息來自該上下文提供者。 - 將回
ProvideAIContextAsync傳的訊息、工具與指令與現有的合併,產生代理將使用的輸入。 訊息、工具和指示會附加在現有的指令上。
InvokedCoreAsync底座執行以下功能:
- 檢查執行是否失敗,若失敗則返回,無需進一步處理。
- 它會過濾輸入訊息清單,只篩選出呼叫者傳送到代理的訊息。 請注意,此篩選可以透過
AIContextProvider建構函式上的storeInputMessageFilter參數來覆寫。 - 將過濾過的請求訊息及所有回應訊息傳送至
StoreAIContextAsync儲存。
您可以覆寫這些方法來實作 AIContextProvider,不過這需要實作者自行適當地實作基礎功能。
這裡有一個此類實作的範例。
internal sealed class AdvancedServiceMemoryProvider : AIContextProvider
{
private readonly ProviderSessionState<State> _sessionState;
private readonly ServiceClient _client;
public AdvancedServiceMemoryProvider(ServiceClient client, Func<AgentSession?, State>? stateInitializer = null)
: base(null, null)
{
this._sessionState = new ProviderSessionState<State>(
stateInitializer ?? (_ => new State()),
this.GetType().Name);
this._client = client;
}
public override string StateKey => this._sessionState.StateKey;
protected override async ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
var state = this._sessionState.GetOrInitializeState(context.Session);
if (state.MemoriesId == null)
{
// No stored memories yet.
return new AIContext();
}
// We only want to search for memories based on user input, and exclude chat history or other AI context provider messages.
var filteredInputMessages = context.AIContext.Messages?.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External);
// Find memories that match the current user input.
var memories = this._client.LoadMemories(state.MemoriesId, string.Join("\n", filteredInputMessages?.Select(x => x.Text) ?? []));
// Create a message for the memories, and stamp it to indicate where it came from.
var memoryMessages =
[new ChatMessage(ChatRole.User, "Here are some memories to help answer the user question: " + string.Join("\n", memories.Select(x => x.Text)))]
.Select(m => m.WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!));
// Return a new merged AIContext.
return new AIContext
{
Instructions = context.AIContext.Instructions,
Messages = context.AIContext.Messages.Concat(memoryMessages),
Tools = context.AIContext.Tools
};
}
protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
if (context.InvokeException is not null)
{
return;
}
var state = this._sessionState.GetOrInitializeState(context.Session);
// Create a memory container in the service for this session
// and save the returned id in the session.
state.MemoriesId ??= this._client.CreateMemoryContainer();
this._sessionState.SaveState(context.Session, state);
// We only want to store memories based on user input and agent output, and exclude messages from chat history or other AI context providers to avoid feedback loops.
var filteredRequestMessages = context.RequestMessages.Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.External);
// Use the service to extract memories from the user input and agent response.
await this._client.StoreMemoriesAsync(state.MemoriesId, filteredRequestMessages.Concat(context.ResponseMessages ?? []), cancellationToken);
}
public class State
{
public string? MemoriesId { get; set; }
}
}
from typing import Any
from agent_framework import AgentSession, ContextProvider, SessionContext
class UserPreferenceProvider(ContextProvider):
def __init__(self) -> None:
super().__init__("user-preferences")
async def before_run(
self,
*,
agent: Any,
session: AgentSession,
context: SessionContext,
state: dict[str, Any],
) -> None:
if favorite := state.get("favorite_food"):
context.extend_instructions(self.source_id, f"User's favorite food is {favorite}.")
async def after_run(
self,
*,
agent: Any,
session: AgentSession,
context: SessionContext,
state: dict[str, Any],
) -> None:
for message in context.input_messages:
text = (message.text or "") if hasattr(message, "text") else ""
if isinstance(text, str) and "favorite food is" in text.lower():
state["favorite_food"] = text.split("favorite food is", 1)[1].strip().rstrip(".")
備註
ContextProvider 和 HistoryProvider 是標準的Python基底類別。
內容提供者也可以透過呼叫 context.extend_middleware(self.source_id, middleware) 為目前呼叫加入聊天或函式中介軟體。 代理程式會使用 context.get_middleware() 將這些新增內容扁平化,並依照提供者順序套用,然後再呼叫聊天用戶端。
動態工具選擇
內容提供者可以使用 context.extend_tools(self.source_id, tools) 為目前叫用新增工具。 關於函式呼叫迴圈中的漸進式工具載入,請參見 dynamic_tool_exposure範例。 關於受管工具包,請參見 Microsoft Foundry 工具箱。
自訂歷史提供者
歷史提供者是專門用於載入/儲存訊息的上下文提供者。
from collections.abc import Sequence
from typing import Any
from agent_framework import HistoryProvider, Message
class DatabaseHistoryProvider(HistoryProvider):
def __init__(self, db: Any) -> None:
super().__init__("db-history", load_messages=True)
self._db = db
async def get_messages(
self,
session_id: str | None,
*,
state: dict[str, Any] | None = None,
**kwargs: Any,
) -> list[Message]:
key = (state or {}).get("history_key", session_id or "default")
rows = await self._db.load_messages(key)
return [Message.from_dict(row) for row in rows]
async def save_messages(
self,
session_id: str | None,
messages: Sequence[Message],
*,
state: dict[str, Any] | None = None,
**kwargs: Any,
) -> None:
if not messages:
return
if state is not None:
key = state.setdefault("history_key", session_id or "default")
else:
key = session_id or "default"
await self._db.save_messages(key, [m.to_dict() for m in messages])
這很重要
在 Python 中,你可以設定多個歷史提供者,但 只有一個 應該使用 load_messages=True。
透過 load_messages=False 和 store_context_messages=True 使用其他提供者進行診斷/評估,以便他們在輸入/輸出中同時擷取來自其他提供者的內容。
如果你需要在工具迴圈中每個模型呼叫周圍持續保留本地歷史,請參見 儲存。
範例模式:
primary = DatabaseHistoryProvider(db)
audit = InMemoryHistoryProvider("audit", load_messages=False, store_context_messages=True)
agent = Agent(client=OpenAIChatClient(), context_providers=[primary, audit])
定義一個帶有 Provide 回調的自訂上下文提供者:
import (
"context"
"github.com/microsoft/agent-framework-go/agent"
"github.com/microsoft/agent-framework-go/message"
)
provider := agent.NewContextProvider(agent.ContextProviderConfig{
SourceID: "user_memory",
Provide: func(ctx context.Context, invoking agent.InvokingContext) ([]*message.Message, []agent.Option, error) {
return nil, []agent.Option{agent.WithInstructions("User prefers short answers.")}, nil
},
})
上下文提供者可以讀寫會話狀態:
Provide: func(ctx context.Context, invoking agent.InvokingContext) ([]*message.Message, []agent.Option, error) {
session, _ := agent.GetOption(invoking.Options, agent.WithSession)
var state MyState
_, _ = session.Get("my_key", &state)
return nil, nil, nil
},
Store: func(ctx context.Context, invoked agent.InvokedContext) error {
session, _ := agent.GetOption(invoked.Options, agent.WithSession)
session.Set("my_key", updatedState)
return nil
},