MAF 自动处理邮件-多分支

using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using System.Text.Json;

namespace ConsoleApp1.WorksFolw
{
    //参考地址:  https://www.cnblogs.com/edisontalk/p/-/quick-start-on-maf-chatper09
    public static class Testdu多条件分支
    {

        public static async Task Test正常邮件()
        {
            var workflow = await CreateWorkflowB();
            var scenarioName1 = "正常邮件 → EmailAssistant → SendEmail";
            var emailContent1 = @"
            尊敬的客服团队:
            您好!我是贵公司的长期客户,订单号为 
            #2025
            -001。
            我想确认一下上周提交的采购订单是否已经安排发货。
            如果需要补充任何信息,请随时告知。
            期待您的回复,谢谢!
            客户:张先生
            ";
            await RunWorkflowAsync(workflow, scenarioName1, emailContent1);
            Console.WriteLine("✅ 正常邮件路径验证完成");
        }
        public static async Task Test垃圾邮件()
        {
            var workflow = await CreateWorkflowB();
            var scenarioName2 = "垃圾邮件 → HandleSpam";
            var emailContent2 = @"
                🎉🎉🎉 恭喜您中奖啦!🎉🎉🎉
                您已被选中获得 100 万现金大奖!
                立即点击以下链接领取:
                http://suspicious-site.com/claim-prize
                仅限今日有效,过期作废!
                不需要任何手续费,完全免费!
                快速行动,机不可失!
                ";
            await RunWorkflowAsync(workflow, scenarioName2, emailContent2);
            Console.WriteLine("✅ 垃圾邮件路径验证完成");
        }

        public static async Task Test不确定邮件()
        {
            var workflow = await CreateWorkflowB();
            var uncertainEmail = @"
            主题:需要验证您的账户
            尊敬的客户:
            我们检测到您的账户存在异常活动,需要验证您的身份以确保账户安全。
            请登录您的账户并完成验证流程,以继续使用服务。
            账户详情:
            - 用户名:johndoe@contoso.com
            - 最后登录:08/15/2025
            - 登录地点:西雅图,华盛顿州
            - 登录设备:移动设备
            这是一项自动安全措施。如果您认为此邮件是错误发送的,请立即联系我们的支持团队。
            此致
            安全团队
            客户服务部门
            ";
            await RunWorkflowAsync(
                workflow,
                "不确定邮件 → HandleUncertain (Default)",
                uncertainEmail
            );
            Console.WriteLine("✅ 不确定邮件路径验证完成");
        }

        static async Task RunWorkflowAsync( Workflow workflow, string scenarioName,string emailContent)
        {
            Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            Console.WriteLine($"📬 测试场景:{scenarioName}");
            Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            Console.WriteLine($"📧 邮件内容:{emailContent.Substring(0, Math.Min(80, emailContent.Length))}...\n");
            await using var run = await InProcessExecution.RunStreamingAsync(
                workflow,
                new ChatMessage(ChatRole.User, emailContent)
            );
            // 发送 Turn Token,启用事件推送
            await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
            // 订阅事件流
            await foreach (WorkflowEvent evt in run.WatchStreamAsync())
            {
                switch (evt)
                {
                    case ExecutorCompletedEvent completedEvent:
                        Console.WriteLine($"✅ {completedEvent.ExecutorId} 完成");
                        break;
                    case WorkflowOutputEvent outputEvent:
                        Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
                        Console.WriteLine("🎉 工作流执行完成");
                        Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
                        Console.WriteLine($"{outputEvent.Data}");
                        break;
                    case WorkflowErrorEvent errorEvent:
                        Console.WriteLine("✨ 收到 Workflow Error Event:");
                        Console.WriteLine($"{errorEvent.Data}");
                        break;
                    default:
                        break;
                }
            }
            Console.WriteLine();
        }


        public static async Task<Workflow> CreateWorkflowB()
        {
            AIService chatClient = new AIService();
            var param = new ChatClientAgentOptions
            {
                Name = "ttt",
                ChatOptions = new()
                {
                    Instructions = @"你是一个垃圾邮件检测助手。判定规则:" +
                    "- NotSpam: 明显的正常业务邮件(订单查询、售后咨询等)" +
                    "- Spam: 明显的垃圾邮件(诈骗、广告、钓鱼)" +
                    "- Uncertain: 无法明确判断,包含可疑元素但不确定(如含可疑链接但内容模糊)" +
                    "对于模棱两可的情况,倾向于标记为 Uncertain 以保证安全。\"",
                    ResponseFormat = ChatResponseFormat.ForJsonSchema<DetectionResult>()
                }
            };
            var spamDetectionAgent = chatClient.GetAIAgentByParms(param);
            var param2 = new ChatClientAgentOptions
            {
                Name = "tt2222",
                ChatOptions = new()
                {
                    Instructions = "您是一名企业电子邮件助理。您可以为用户提供专业的中文回复。",
                    ResponseFormat = ChatResponseFormat.ForJsonSchema<EmailResponse>()
                }
            };
            var emailAssistantAgent = chatClient.GetAIAgentByParms(param2);


            //垃圾邮件检测助手   邮件检测执行器
            var spamDetectionExecutor = new SpamDetectionExecutor(spamDetectionAgent);
            //邮件助手执行器q
            var emailAssistantExecutor = new EmailAssistantExecutor(emailAssistantAgent);
            // 发送邮件执行器
            var sendEmailExecutor = new EmailSendingExecutor();
            //垃圾邮件处理执行器
            var handleSpamExecutor = new SpamHandlingExecutor();

            var handleUncertainExecutor = new UncertainHandlingExecutor();

            //Func<object?, bool> GetCondition(SpamDecision expectedDecision) =>
            //     detectionResult =>
            //         detectionResult is DetectionResult result &&
            //         result.spamDecision == expectedDecision;

            Func<object, bool> GetCondition(SpamDecision expectedDecision)
            {
                // 使用 C# 2.0 的匿名方法代替 Lambda 表达式
                return delegate (object detectionResult)
                {
                    // 使用传统的 is 检查 + 显式强制转换代替 C# 7.0 的模式匹配
                    if (detectionResult is DetectionResult)
                    {
                        DetectionResult result = (DetectionResult)detectionResult;
                        return result.spamDecision == expectedDecision;
                    }

                    return false;
                };
            }


            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            // 🔀 使用 AddSwitch 构建 Switch-Case 工作流
            // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
            var builder = new WorkflowBuilder(spamDetectionExecutor);
           
            builder.AddSwitch(spamDetectionExecutor, sb =>
                    sb
                        // Case 1: NotSpam → EmailAssistant 
                        .AddCase(GetCondition(expectedDecision: SpamDecision.NotSpam), new[] { (ExecutorBinding)emailAssistantExecutor })
                        // Case 2: Spam → HandleSpam
                        .AddCase(GetCondition(expectedDecision: SpamDecision.Spam), new[] { (ExecutorBinding)handleSpamExecutor })
                        // Default: Uncertain (或任何未匹配的情况) → HandleUncertain
                        .WithDefault(new[] { (ExecutorBinding)handleUncertainExecutor })
                )
                // EmailAssistant 之后自动发送邮件
                .AddEdge(emailAssistantExecutor, sendEmailExecutor)
                // 配置输出节点(三个终点执行器都会产生输出)
                .WithOutputFrom(handleSpamExecutor, sendEmailExecutor, handleUncertainExecutor);
            var workflow = builder.Build();



            Console.OutputEncoding = Encoding.UTF8;
            Console.WriteLine(" Conditional Workflow 构建完成");
            return workflow;
        }



    }
    public sealed class DetectionResult
    {
        /// <summary>
        /// 检测决策(NotSpam / Spam / Uncertain)
        /// </summary>
        [JsonPropertyName("spam_decision")]
        [JsonConverter(typeof(JsonStringEnumConverter))]  // JSON 序列化为字符串
        public SpamDecision spamDecision { get; set; }
        /// <summary>
        /// 判定理由(用于审计和调试)
        /// </summary>
        [JsonPropertyName("reason")]
        public string Reason { get; set; } = string.Empty;
        /// <summary>
        /// 邮件ID(用于关联 Shared State 中的原始内容)
        /// </summary>
        [JsonIgnore]
        public string EmailId { get; set; } = string.Empty;
    }
    internal static class EmailStateConstants
    {
        public const string EmailStateScope = "EmailState";
    }


    internal sealed class EmailMessage
    {
        [JsonPropertyName("email_id")]
        public string EmailId { get; set; } = string.Empty;

        [JsonPropertyName("email_content")]
        public string EmailContent { get; set; } = string.Empty;
    }

    public sealed class EmailResponse
    {
        [JsonPropertyName("response")]
        public string Response { get; set; } = string.Empty;
    }
    public enum SpamDecision
    {
        Spam,        // 垃圾邮件
        NotSpam, // 正常邮件
        UnCertain // 无法确定(需要人工审核)
    }

    #region 执行器
    internal sealed class SpamDetectionExecutor : Executor<ChatMessage, DetectionResult>
    {
        private readonly AIAgent _agent;
        private  AgentSession _thread;
        public SpamDetectionExecutor(AIAgent agent) : base("SpamDetectionExecutor")
        {
            // 创建 Agent 和对话线程
            this._agent = agent;
            //this._thread = this._agent.GetNewThread();
        }
        public override async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
        {
            if (this._thread == null)
            {
                this._thread = await this._agent.CreateSessionAsync();
            }
            // 1️⃣ 生成唯一邮件ID并保存内容到 Shared State
            var trackedEmail = new EmailMessage
            {
                EmailId = Guid.NewGuid().ToString("N"),
                EmailContent = message.Text
            };
            await context.QueueStateUpdateAsync(
                trackedEmail.EmailId,
                trackedEmail,
                scopeName: EmailStateConstants.EmailStateScope,
                cancellationToken
            );
            // 2️⃣ 调用 AI Agent 进行三分类检测
            var agentResponse = await _agent.RunAsync(
                message,
                _thread,
                cancellationToken: cancellationToken
            );
            // 3️⃣ 解析结构化输出
            var detection = JsonSerializer.Deserialize<DetectionResult>(agentResponse.Text)
                ?? throw new InvalidOperationException("无法解析 Spam Detection 响应");
            // 4️⃣ 关联 EmailId(供下游 Executor 查找原始内容)
            detection.EmailId = trackedEmail.EmailId;
            return detection;
        }
    }

    //正常邮件处理
//    下游节点A:正常邮件处理+发送
//这里我们针对识别到的正常邮件开发两个执行器,假设其用于邮件处和转发:
//邮件处理:读取共享状态区的原文,然后调用Agent输出JSON回复
    internal sealed class EmailAssistantExecutor : Executor<DetectionResult, EmailResponse>
    {
        private readonly AIAgent _agent;
        private AgentSession _thread;

        public EmailAssistantExecutor(AIAgent agent) : base("EmailAssistantExecutor")
        {
            // 创建 Agent 和对话线程
            this._agent = agent;
           
        }

        public override async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
        {
            if (this._thread == null)
            {
                this._thread = await this._agent.CreateSessionAsync();
            }
            // 🛡️ 防御性检查:确保只处理正常邮件
            if (message.spamDecision == SpamDecision.Spam)
                throw new InvalidOperationException(
                    "EmailAssistantExecutor 不应处理垃圾邮件,请检查路由配置。"
                );

            // 1️⃣ 从 Shared State 读取原始邮件内容
            var email = await context.ReadStateAsync<EmailMessage>(
                message.EmailId,
                scopeName: EmailStateConstants.EmailStateScope,
                cancellationToken
            ) ?? throw new InvalidOperationException($"找不到 EmailId={message.EmailId} 的邮件内容");

            // 2️⃣ 调用 AI Agent 生成回复
            var agentResponse = await _agent.RunAsync(
                email.EmailContent,
                _thread,
                cancellationToken: cancellationToken
            );

            // 3️⃣ 解析结构化输出
            var emailResponse = JsonSerializer.Deserialize<EmailResponse>(agentResponse.Text)
                ?? throw new InvalidOperationException("无法解析 Email Assistant 响应");

            return emailResponse;
        }
    }
    internal sealed class EmailSendingExecutor() : Executor<EmailResponse>("EmailSendingExecutor")
    {
        public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default)
        {
            Console.WriteLine($"📤 邮件已发送-----------: {message.Response}");
            // 模拟邮件发送(实际项目中可调用 SMTP、SendGrid 等服务)
            await context.YieldOutputAsync(
                $"📤 邮件已发送: {message.Response}",
                cancellationToken
            );
        }
    }

    //    下游节点B:垃圾邮件处理
    //当判断到是垃圾邮件时,转交给该执行器处理,这里模拟输出了一段风险提示,实际中可能是上报人工跟进等等操作:
    internal sealed class SpamHandlingExecutor() : Executor<DetectionResult>("SpamHandlingExecutor")
    {
        public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
        {
            // 🛡️ 防御性检查:确保只处理垃圾邮件
            if (message.spamDecision != SpamDecision.Spam)
                throw new InvalidOperationException(
                    "SpamHandlingExecutor 只应处理 Spam 类型的邮件,请检查路由配置。"
                );

            // 记录垃圾邮件(实际项目中可写入数据库或日志系统)
            await context.YieldOutputAsync(
                $"🚫 垃圾邮件已拦截: {message.Reason}",
                cancellationToken
            );
        }
    }


    //    下游节点C:不确定邮件处理执行器(兜底处理)
    //当判断到属于不确定的邮件分类时,转交给该执行器做兜底处理 或 默认处理:
    internal class UncertainHandlingExecutor() : Executor<DetectionResult>("UncertainHandlingExecutor")
    {
        public override async ValueTask HandleAsync(
            DetectionResult message,
            IWorkflowContext context,
            CancellationToken cancellationToken = default)
        {
            // 🛡️ 防御性检查:确保只处理不确定邮件
            if (message.spamDecision != SpamDecision.UnCertain)
                throw new InvalidOperationException(
                    "UncertainHandlingExecutor 只应处理 Uncertain 类型的邮件(或作为 Default Case)。"
                );

            // 1️⃣ 从 Shared State 读取原始邮件内容(用于人工审核)
            var email = await context.ReadStateAsync<EmailMessage>(
                message.EmailId,
                scopeName: EmailStateConstants.EmailStateScope,
                cancellationToken
            );

            // 2️⃣ 输出待审核信息
            await context.YieldOutputAsync(
                $"⚠️ 不确定邮件需人工审核:\n" +
                $"原因: {message.Reason}\n" +
                $"内容预览: {email?.EmailContent?.Substring(0, Math.Min(100, email.EmailContent.Length))}...",
                cancellationToken
            );
        }
    }

    #endregion








}

  

posted @ 2026-09-17 15:25  陌念  阅读(7)  评论(0)    收藏  举报