You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

context.Wait()不保留父类属性致节点链循环异常技术问询

解决Bot Framework中循环节点链的序列化与流程跳转问题

我之前也碰到过一模一样的循环节点链序列化坑,结合Bot Framework的状态序列化机制,给你两个可行的解决方案:

核心问题分析

你的问题根源在于循环引用的序列化冲突:

  1. 虽然给Node加了[JsonObject(IsReference = true)],但Bot Framework的状态序列化逻辑会主动截断循环引用,导致OnCompleteNode这类属性被清空;
  2. 重写基类虚拟属性后,破坏了序列化的契约,导致context.Wait无法正确绑定后续节点的回调,直接回到RootDialog;
  3. 直接持有节点对象引用的循环链,在序列化时会被框架判定为"不安全"结构,从而丢失关联关系。

方案一:用节点ID替代对象引用(推荐,最稳定)

放弃直接持有OnCompleteNode对象,改用节点ID来管理跳转关系,从根源上避免循环序列化问题。

1. 修改Node基类,改用ID管理跳转

[Serializable]
[JsonObject(IsReference = true)]
public class Node : INode
{
    public virtual int ID { get; set; }
    public virtual string Name { get; set; }
    public virtual bool WaitForInput { get; set; }
    public virtual int OnCompleteID { get; set; } // 用ID替代直接节点引用

    // 静态节点池:存储所有节点,也可以存在Dialog状态或外部存储中
    public static Dictionary<int, Node> NodePool = new Dictionary<int, Node>();

    public virtual async Task OnExecute(IDialogContext context, Activity activity)
    {
        // 通过ID从节点池获取下一个节点
        if (NodePool.TryGetValue(OnCompleteID, out Node nextNode))
        {
            if (WaitForInput)
                context.Wait(nextNode.OnExecuteAwait);
            else
                await nextNode.OnExecute(context, activity);
        }
    }

    public virtual async Task OnExecuteAwait(IDialogContext context, IAwaitable<object> result)
    {
        Activity activity = await result as Activity;
        await OnExecute(context, activity);
    }
}

2. 简化MessageNode子类(无需重写基类属性)

public class MessageNode : Node
{
    public virtual string Message { get; set; }
    public virtual List<Attachment> Attachments { get; set; }
    public virtual string AttachmentLayoutType { get; set; }

    public override async Task OnExecute(IDialogContext context, Activity activity)
    {
        IMessageActivity reply = context.MakeMessage();
        reply.Text = Message;
        if (Attachments != null)
        {
            reply.Attachments = Attachments;
            reply.AttachmentLayout = AttachmentLayoutType;
        }
        await context.PostAsync(reply);
        await base.OnExecute(context, activity);
    }
}

3. 在RootDialog中构建循环链并填充节点池

public Task StartAsync(IDialogContext context)
{
    // 初始化节点池(如果是多会话场景,建议把节点池存在会话状态里)
    Node.NodePool.Clear();

    // 创建节点并设置ID跳转关系
    var messageNode = new Schema.MessageNode
    {
        Message = "test",
        ID = 0,
        OnCompleteID = 1, // 跳转到ID=1的节点
        WaitForInput = true
    };
    Node.NodePool.Add(messageNode.ID, messageNode);

    var preMN = new Schema.MessageNode
    {
        Message = "and then",
        ID = 1,
        OnCompleteID = 2, // 跳转到ID=2的节点
        WaitForInput = true
    };
    Node.NodePool.Add(preMN.ID, preMN);

    var preMN1 = new Schema.MessageNode
    {
        Message = "and then one more",
        ID = 2,
        OnCompleteID = 0, // 跳回ID=0的节点,形成循环
        WaitForInput = true
    };
    Node.NodePool.Add(preMN1.ID, preMN1);

    // 只需要把当前节点ID存入状态,无需序列化整个链
    context.UserData.SetValue("CurrentNodeID", messageNode.ID);
    context.Wait(messageNode.OnExecuteAwait);
    return Task.CompletedTask;
}

4. 可选:从状态中恢复节点(持久化场景)

如果需要跨会话保留节点状态,不要序列化整个链,只存当前节点ID,每次从节点池获取:

protected override async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
    if (context.UserData.TryGetValue("CurrentNodeID", out int currentNodeId)
        && Node.NodePool.TryGetValue(currentNodeId, out Node currentNode))
    {
        await currentNode.OnExecuteAwait(context, result);
    }
}

方案二:强制开启循环引用序列化(不推荐,仅临时场景用)

如果一定要保留对象引用,可以调整Bot的全局Json序列化配置,强制保留循环引用,但可能导致状态体积过大,且部分Bot版本仍可能出现截断问题:

1. 在Bot初始化时配置Json序列化

var config = new HttpConfiguration();
// 允许序列化循环引用
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
// 保留对象引用标识
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;

2. 确保Node类属性不被重写隐藏

不要在MessageNode中重写基类的虚拟属性,保持继承关系的完整性,让序列化框架能正确识别属性契约。


内容的提问来源于stack exchange,提问作者Manmax75

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:27:33