using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.Agents.AI; using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; namespace MAF1.Route; /// /// 跑 Handoff。工具日志按 callId 去重:同一调用会同时出现在流式事件、完整回复、结束输出里,只打一遍。 /// public static class HandoffRun { private static readonly JsonSerializerOptions JsonOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; public static async Task RunAsync(Workflow workflow, string task) { HashSet printedCalls = new(StringComparer.Ordinal); List messages = [new(ChatRole.User, task)]; await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, messages); await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); string? lastExecutorId = null; string? lastSummary = null; await foreach (WorkflowEvent evt in run.WatchStreamAsync()) { switch (evt) { case ExecutorFailedEvent failed: Console.WriteLine(); Console.WriteLine($"[失败] {failed.ExecutorId}: {failed.Data}"); break; case AgentResponseUpdateEvent update: if (update.ExecutorId != lastExecutorId) { Console.WriteLine(); Console.Write($"{Friendly(update.ExecutorId)}: "); lastExecutorId = update.ExecutorId; } if (!string.IsNullOrEmpty(update.Update.Text)) { Console.Write(update.Update.Text); } WriteNewTools(update.ExecutorId, update.Update.Contents, printedCalls); break; case WorkflowOutputEvent output: string? summary = ReadSummary(output); if (!string.IsNullOrWhiteSpace(summary) && summary != lastSummary) { lastSummary = summary; Console.WriteLine(); Console.WriteLine(); Console.WriteLine($"[最终输出] {Trim(summary)}"); } break; } } Console.WriteLine(); Console.WriteLine("======== Handoff 本轮结束 ========"); } private static void WriteNewTools(string executorId, IList contents, HashSet printedCalls) { foreach (AIContent content in contents) { if (content is FunctionCallContent call) { string key = "call:" + (string.IsNullOrWhiteSpace(call.CallId) ? call.Name + FormatArguments(call.Arguments) : call.CallId); if (!printedCalls.Add(key)) { continue; } Console.WriteLine(); Console.WriteLine($"[工具调用] {Friendly(executorId)} → {call.Name}"); Console.WriteLine($" 参数: {FormatArguments(call.Arguments)}"); continue; } if (content is FunctionResultContent result) { string key = "result:" + result.CallId; if (!printedCalls.Add(key)) { continue; } Console.WriteLine(); Console.WriteLine($"[工具返回] {Friendly(executorId)} → {result.CallId}"); Console.WriteLine($" 结果: {Trim(FormatResult(result.Result))}"); } } } private static string? ReadSummary(WorkflowOutputEvent output) { if (output.As>() is List chat) { return LastAssistantText(chat); } return output.As(); } private static string FormatArguments(IDictionary? arguments) { if (arguments is null || arguments.Count == 0) { return "{}"; } try { return JsonSerializer.Serialize(arguments, JsonOptions); } catch { return string.Join(", ", arguments.Select(kv => $"{kv.Key}={kv.Value}")); } } private static string FormatResult(object? result) => result is null ? "(空)" : result.ToString() ?? "(空)"; private static string LastAssistantText(List chat) { string text = ""; foreach (ChatMessage message in chat) { if (message.Role == ChatRole.Assistant && !string.IsNullOrWhiteSpace(message.Text)) { text = message.Text; } } return text; } private static string Friendly(string executorId) { int us = executorId.IndexOf('_'); if (us > 0 && executorId[(us + 1)..] == executorId[..us]) { return executorId[..us]; } return executorId; } private static string Trim(string text) { string oneLine = text.Replace("\r\n", " ").Replace('\n', ' ').Trim(); return oneLine.Length <= 500 ? oneLine : oneLine[..500] + "…"; } }