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

C#版Bot Framework v4如何结合对话框与其他命令

问题根因

核心问题是消息路由逻辑顺序错误:

  • 所有用户消息进入后,未优先判断是否存在正在执行的多轮对话框,直接将所有输入作为顶层命令做switch匹配
  • 用户发送details启动瀑布对话框后,后续输入的订单号是数字字符串,匹配不到help/orders/details三个预设命令,直接命中default分支,消息根本没有传递给已启动的对话框实例,导致对话流中断
  • 另外OrderDetailDialog中存在一处类型转换bug,即使消息路由修复,执行到第二步也会抛出运行时异常
修复方案

1. 调整TroyBot的消息路由优先级

首先将DialogState存取器提取为类级字段在构造函数中初始化,收到消息后优先判断是否存在活跃对话框,存在则直接将消息交付对话框处理,无活跃对话框时再走顶层命令匹配逻辑。
修正后的TroyBot核心代码:

public class TroyBot<T> : ActivityHandler where T : Dialog
{
    private readonly ConversationState _conversationState;
    private readonly UserState _userState;
    private readonly T _dialog;
    // 提前声明对话框状态存取属性
    private readonly IStatePropertyAccessor<DialogState> _dialogStateAccessor;

    public TroyBot(ConversationState conversationState, UserState userState, T dialog)
    {
        _conversationState = conversationState;
        _userState = userState;
        _dialog = dialog;
        // 初始化对话框状态存取器
        _dialogStateAccessor = _conversationState.CreateProperty<DialogState>(nameof(DialogState));
    }

    protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
    {
        foreach (var member in membersAdded)
        {
            if (member.Id != turnContext.Activity.Recipient.Id)
            {
                await turnContext.SendActivityAsync(MessageFactory.Text(
                    "Hi! I'm Troy. I will help you track and manage orders." +
                    "\n\n" +
                    "Say **`help`** to see what I can do."
                ), cancellationToken);
            }
        }
    }

    protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
    {
        // 优先判断是否有正在运行的对话框
        var dialogState = await _dialogStateAccessor.GetAsync(turnContext, () => new DialogState(), cancellationToken);
        // 对话框栈不为空时,直接将当前轮次消息交付给对话框承接流程
        if (dialogState.DialogStack.Any())
        {
            await _dialog.RunAsync(turnContext, _dialogStateAccessor, cancellationToken);
            return;
        }

        // 无活跃对话框时才匹配顶层命令
        var command = turnContext.Activity.Text.Trim().ToLower();
        switch (command)
        {
            case "orders":
                var orders = await GetOrders();
                Attachment ordersAttachment = GetOrdersAttachment(orders);
                await turnContext.SendActivityAsync(MessageFactory.Attachment(ordersAttachment), cancellationToken);
                break;

            case "help":
                var helpText = GetHelpText();
                await turnContext.SendActivityAsync(MessageFactory.Text(helpText), cancellationToken);
                break;

            case "details":
                var detailOrders = await GetOrders();
                Attachment detailOrdersAttachment = GetOrdersAttachment(detailOrders);
                await turnContext.SendActivityAsync(MessageFactory.Attachment(detailOrdersAttachment), cancellationToken);
                await _dialog.RunAsync(turnContext, _dialogStateAccessor, cancellationToken);
                break;

            default:
                await turnContext.SendActivityAsync(MessageFactory.Text(
                    "Hmmm... I don't know how to help you with that.\n\n" +
                    "Try saying `help` to see what I can do."
                ), cancellationToken);
                break;
        }
    }

    public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
    {
        await base.OnTurnAsync(turnContext, cancellationToken);
        // 保持原有状态保存逻辑即可,不要在此处额外调用dialog.RunAsync,避免重复执行
        await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
        await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
    }

    // 其余自定义工具方法(GetOrders、GetOrdersAttachment、GetHelpText等)保持不变
}

2. 修复OrderDetailDialog的类型转换bug

你使用的是NumberPrompt<int>,其返回结果是int类型值,FoundChoice是ChoicePrompt专用的返回类型,直接强转会抛出类型不匹配异常,修改对应瀑布步代码:

private static async Task<DialogTurnResult> ViewOrderDetailsStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    // 直接取int类型的订单号
    var orderNumber = (int)stepContext.Result;
    stepContext.Values["orderNumber"] = orderNumber;

    // 可在此处加入根据orderNumber查询对应订单的逻辑,再生成匹配的详情卡片
    return await stepContext.EndDialogAsync(GetOrderDetailsCard(), cancellationToken);
}

如果后续需要根据传入的订单号动态生成详情卡片,记得去掉GetOrderDetailsCard()方法的static修饰,接入订单仓储查询逻辑即可。

执行流说明

修复后的运行逻辑:

  • 用户发送details时,对话框栈为空,走对应命令分支,发送订单列表后启动对话框,对话框栈压入对应实例,等待用户输入
  • 用户发送订单号时,检测到对话框栈非空,直接将消息交付给对话框,NumberPrompt完成输入校验后进入下一个瀑布步,返回订单详情后清空对话框栈
  • 对话框结束后栈为空,后续用户输入会重新进入顶层命令匹配逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 16:45:38