mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
401e5dc7e8
* Allow Simulating service stored ChatHistory to improve consistency * Fixing bug in ServiceStoredSimulatingChatClient * Addressing PR comments. * Address PR comments * Apply suggestion from @SergeyMenshykh Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Fix bug --------- Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
229 lines
9.5 KiB
C#
229 lines
9.5 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
// This sample demonstrates how the ChatClientAgent persists chat history after each individual
|
|
// call to the AI service, using the SimulateServiceStoredChatHistory option.
|
|
// When an agent uses tools, FunctionInvokingChatClient may loop multiple times
|
|
// (service call → tool execution → service call), and intermediate messages (tool calls and
|
|
// results) are persisted after each service call. This allows you to inspect or recover them
|
|
// even if the process is interrupted mid-loop, but may also result in chat history that is not
|
|
// yet finalized (e.g., tool calls without results) being persisted, which may be undesirable in some cases.
|
|
//
|
|
// To use end-of-run persistence instead (atomic run semantics), remove the
|
|
// SimulateServiceStoredChatHistory = true setting (or set it to false). End-of-run
|
|
// persistence is the default behavior.
|
|
//
|
|
// The sample runs two multi-turn conversations: one using non-streaming (RunAsync) and one
|
|
// using streaming (RunStreamingAsync), to demonstrate correct behavior in both modes.
|
|
|
|
using System.ComponentModel;
|
|
using Azure.AI.OpenAI;
|
|
using Azure.Identity;
|
|
using Microsoft.Agents.AI;
|
|
using Microsoft.Extensions.AI;
|
|
using OpenAI.Responses;
|
|
|
|
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
|
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
|
var store = Environment.GetEnvironmentVariable("AZURE_OPENAI_RESPONSES_STORE") ?? "false";
|
|
|
|
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
|
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
|
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
|
AzureOpenAIClient openAIClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
|
|
// Define multiple tools so the model makes several tool calls in a single run.
|
|
[Description("Get the current weather for a city.")]
|
|
static string GetWeather([Description("The city name.")] string city) =>
|
|
city.ToUpperInvariant() switch
|
|
{
|
|
"SEATTLE" => "Seattle: 55°F, cloudy with light rain.",
|
|
"NEW YORK" => "New York: 72°F, sunny and warm.",
|
|
"LONDON" => "London: 48°F, overcast with fog.",
|
|
"DUBLIN" => "Dublin: 43°F, overcast with fog.",
|
|
_ => $"{city}: weather data not available."
|
|
};
|
|
|
|
[Description("Get the current time in a city.")]
|
|
static string GetTime([Description("The city name.")] string city) =>
|
|
city.ToUpperInvariant() switch
|
|
{
|
|
"SEATTLE" => "Seattle: 9:00 AM PST",
|
|
"NEW YORK" => "New York: 12:00 PM EST",
|
|
"LONDON" => "London: 5:00 PM GMT",
|
|
"DUBLIN" => "Dublin: 5:00 PM GMT",
|
|
_ => $"{city}: time data not available."
|
|
};
|
|
|
|
// Create the agent — per-service-call persistence is enabled via SimulateServiceStoredChatHistory.
|
|
// The in-memory ChatHistoryProvider is used by default when the service does not require service stored chat
|
|
// history, so for those cases, we can inspect the chat history via session.TryGetInMemoryChatHistory().
|
|
IChatClient chatClient = string.Equals(store, "TRUE", StringComparison.OrdinalIgnoreCase) ?
|
|
openAIClient.GetResponsesClient().AsIChatClient(deploymentName) :
|
|
openAIClient.GetResponsesClient().AsIChatClientWithStoredOutputDisabled(deploymentName);
|
|
AIAgent agent = chatClient.AsAIAgent(
|
|
new ChatClientAgentOptions
|
|
{
|
|
Name = "WeatherAssistant",
|
|
SimulateServiceStoredChatHistory = true,
|
|
ChatOptions = new()
|
|
{
|
|
Instructions = "You are a helpful assistant. When asked about multiple cities, call the appropriate tool for each city.",
|
|
Tools = [AIFunctionFactory.Create(GetWeather), AIFunctionFactory.Create(GetTime)]
|
|
},
|
|
});
|
|
|
|
await RunNonStreamingAsync();
|
|
await RunStreamingAsync();
|
|
|
|
async Task RunNonStreamingAsync()
|
|
{
|
|
int lastChatHistorySize = 0;
|
|
string lastConversationId = string.Empty;
|
|
|
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
Console.WriteLine("\n=== Non-Streaming Mode ===");
|
|
Console.ResetColor();
|
|
|
|
AgentSession session = await agent.CreateSessionAsync();
|
|
|
|
// First turn — ask about multiple cities so the model calls tools.
|
|
const string Prompt = "What's the weather and time in Seattle, New York, and London?";
|
|
PrintUserMessage(Prompt);
|
|
|
|
var response = await agent.RunAsync(Prompt, session);
|
|
PrintAgentResponse(response.Text);
|
|
PrintChatHistory(session, "After run", ref lastChatHistorySize, ref lastConversationId);
|
|
|
|
// Second turn — follow-up to verify chat history is correct.
|
|
const string FollowUp1 = "And Dublin?";
|
|
PrintUserMessage(FollowUp1);
|
|
|
|
response = await agent.RunAsync(FollowUp1, session);
|
|
PrintAgentResponse(response.Text);
|
|
PrintChatHistory(session, "After second run", ref lastChatHistorySize, ref lastConversationId);
|
|
|
|
// Third turn — follow-up to verify chat history is correct.
|
|
const string FollowUp2 = "Which city is the warmest?";
|
|
PrintUserMessage(FollowUp2);
|
|
|
|
response = await agent.RunAsync(FollowUp2, session);
|
|
PrintAgentResponse(response.Text);
|
|
PrintChatHistory(session, "After third run", ref lastChatHistorySize, ref lastConversationId);
|
|
}
|
|
|
|
async Task RunStreamingAsync()
|
|
{
|
|
int lastChatHistorySize = 0;
|
|
string lastConversationId = string.Empty;
|
|
|
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
Console.WriteLine("\n=== Streaming Mode ===");
|
|
Console.ResetColor();
|
|
|
|
AgentSession session = await agent.CreateSessionAsync();
|
|
|
|
// First turn — ask about multiple cities so the model calls tools.
|
|
const string Prompt = "What's the weather and time in Seattle, New York, and London?";
|
|
PrintUserMessage(Prompt);
|
|
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
Console.Write("\n[Agent] ");
|
|
Console.ResetColor();
|
|
|
|
await foreach (var update in agent.RunStreamingAsync(Prompt, session))
|
|
{
|
|
Console.Write(update);
|
|
|
|
// During streaming we should be able to see updates to the chat history
|
|
// before the full run completes, as each service call is made and persisted.
|
|
PrintChatHistory(session, "During run", ref lastChatHistorySize, ref lastConversationId);
|
|
}
|
|
|
|
Console.WriteLine();
|
|
PrintChatHistory(session, "After run", ref lastChatHistorySize, ref lastConversationId);
|
|
|
|
// Second turn — follow-up to verify chat history is correct.
|
|
const string FollowUp1 = "And Dublin?";
|
|
PrintUserMessage(FollowUp1);
|
|
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
Console.Write("\n[Agent] ");
|
|
Console.ResetColor();
|
|
|
|
await foreach (var update in agent.RunStreamingAsync(FollowUp1, session))
|
|
{
|
|
Console.Write(update);
|
|
|
|
// During streaming we should be able to see updates to the chat history
|
|
// before the full run completes, as each service call is made and persisted.
|
|
PrintChatHistory(session, "During second run", ref lastChatHistorySize, ref lastConversationId);
|
|
}
|
|
|
|
Console.WriteLine();
|
|
PrintChatHistory(session, "After second run", ref lastChatHistorySize, ref lastConversationId);
|
|
|
|
// Third turn — follow-up to verify chat history is correct.
|
|
const string FollowUp2 = "Which city is the warmest?";
|
|
PrintUserMessage(FollowUp2);
|
|
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
Console.Write("\n[Agent] ");
|
|
Console.ResetColor();
|
|
|
|
await foreach (var update in agent.RunStreamingAsync(FollowUp2, session))
|
|
{
|
|
Console.Write(update);
|
|
|
|
// During streaming we should be able to see updates to the chat history
|
|
// before the full run completes, as each service call is made and persisted.
|
|
PrintChatHistory(session, "During third run", ref lastChatHistorySize, ref lastConversationId);
|
|
}
|
|
|
|
Console.WriteLine();
|
|
PrintChatHistory(session, "After third run", ref lastChatHistorySize, ref lastConversationId);
|
|
}
|
|
|
|
void PrintUserMessage(string message)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
Console.Write("\n[User] ");
|
|
Console.ResetColor();
|
|
Console.WriteLine(message);
|
|
}
|
|
|
|
void PrintAgentResponse(string? text)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
Console.Write("\n[Agent] ");
|
|
Console.ResetColor();
|
|
Console.WriteLine(text);
|
|
}
|
|
|
|
// Helper to print the current chat history from the session.
|
|
void PrintChatHistory(AgentSession session, string label, ref int lastChatHistorySize, ref string lastConversationId)
|
|
{
|
|
if (session.TryGetInMemoryChatHistory(out var history) && history.Count != lastChatHistorySize)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.DarkGray;
|
|
Console.WriteLine($"\n [{label} — Chat history: {history.Count} message(s)]");
|
|
foreach (var msg in history)
|
|
{
|
|
var preview = msg.Text?.Length > 80 ? msg.Text[..80] + "…" : msg.Text;
|
|
var contentTypes = string.Join(", ", msg.Contents.Select(c => c.GetType().Name));
|
|
Console.WriteLine($" {msg.Role,-12} | {(string.IsNullOrWhiteSpace(preview) ? $"[{contentTypes}]" : preview)}");
|
|
}
|
|
|
|
Console.ResetColor();
|
|
|
|
lastChatHistorySize = history.Count;
|
|
}
|
|
|
|
if (session is ChatClientAgentSession ccaSession && ccaSession.ConversationId is not null && ccaSession.ConversationId != lastConversationId)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.DarkGray;
|
|
Console.WriteLine($" [{label} — Conversation ID: {ccaSession.ConversationId}]");
|
|
Console.ResetColor();
|
|
lastConversationId = ccaSession.ConversationId;
|
|
}
|
|
}
|