Bot Framework v4:如何重复Waterfall Dialog步骤直至用户输入符合预期?
在Waterfall Dialog中重复步骤直至用户输入符合预期的实现方案
针对你描述的场景(启动搜索→检查参数完整性→补充缺失参数→输出结果),核心是通过对话状态追踪参数+Waterfall步骤的循环逻辑实现重复收集输入,直到满足条件。以下是具体实现方案:
核心思路
- 用对话状态存储已提取的参数和搜索上下文,避免步骤跳转时丢失数据
- 在关键步骤(步骤2)中判断参数是否齐全:
- 若参数缺失,触发Prompt收集用户输入,收集完成后重新进入当前步骤检查
- 若参数齐全,进入最终步骤输出结果
- 利用Waterfall Dialog的内置流程控制(Prompt后的自动回调)实现步骤循环
具体实现(以Bot Framework C#为例)
1. 定义对话状态类
用于存储搜索模板、已提取的参数等上下文信息:
public class SearchDialogState { // 已提取的参数键值对 public Dictionary<string, string> ExtractedParams { get; set; } = new Dictionary<string, string>(); // 包含占位符的搜索模板 public string SearchTemplate { get; set; } }
2. 构建Waterfall Dialog的三个步骤
// 注册文本Prompt用于收集补充参数 var textPrompt = new TextPrompt(nameof(TextPrompt)); Dialogs.Add(textPrompt); // 创建Waterfall Dialog var searchWaterfall = new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { InitializeSearchContextAsync, CheckAndCollectMissingParamsAsync, GenerateAndSendFinalResultAsync }); Dialogs.Add(searchWaterfall); // ------------------------------ // Step 1: 处理初始输入,初始化搜索上下文 // ------------------------------ private async Task<DialogTurnResult> InitializeSearchContextAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { // 获取用户初始输入 var userInput = stepContext.Context.Activity.Text; // 从输入中提取可识别的参数(实际可替换为LUIS/正则等专业识别逻辑) var extractedParams = ExtractParamsFromInput(userInput); // 加载搜索模板(模拟从搜索接口或配置获取) var searchTemplate = "查找{城市}的{星级}酒店"; // 将上下文存入对话状态 var state = await stepContext.Context.TurnState.GetAsync<SearchDialogState>(cancellationToken); state.ExtractedParams = extractedParams; state.SearchTemplate = searchTemplate; // 直接进入参数检查步骤 return await stepContext.NextAsync(null, cancellationToken); } // ------------------------------ // Step 2: 检查参数完整性,缺失则收集补充信息(循环执行) // ------------------------------ private async Task<DialogTurnResult> CheckAndCollectMissingParamsAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var state = await stepContext.Context.TurnState.GetAsync<SearchDialogState>(cancellationToken); var searchTemplate = state.SearchTemplate; var extractedParams = state.ExtractedParams; // 如果是从Prompt返回的结果,先更新已提取的参数 if (stepContext.Result != null) { var userInput = stepContext.Result.ToString(); // 识别当前缺失的参数(实际可通过状态记录当前询问的参数类型) var missingParam = Regex.Matches(searchTemplate, @"\{(\w+)\}") .Cast<Match>() .Select(m => m.Groups[1].Value) .First(p => !extractedParams.ContainsKey(p)); extractedParams[missingParam] = userInput; } // 识别模板中所有占位符 var allPlaceholders = Regex.Matches(searchTemplate, @"\{(\w+)\}") .Cast<Match>() .Select(m => m.Groups[1].Value) .ToList(); // 找出未填充的参数 var missingParams = allPlaceholders.Where(p => !extractedParams.ContainsKey(p)).ToList(); if (!missingParams.Any()) { // 参数齐全,进入最终步骤 return await stepContext.NextAsync(null, cancellationToken); } // 询问第一个缺失的参数 var firstMissingParam = missingParams.First(); var promptMsg = $"请补充{firstMissingParam}信息?"; return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text(promptMsg) }, cancellationToken); } // ------------------------------ // Step 3: 生成并输出最终搜索结果 // ------------------------------ private async Task<DialogTurnResult> GenerateAndSendFinalResultAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var state = await stepContext.Context.TurnState.GetAsync<SearchDialogState>(cancellationToken); var searchTemplate = state.SearchTemplate; var extractedParams = state.ExtractedParams; // 替换模板中的占位符,生成最终结果 var finalResult = searchTemplate; foreach (var param in extractedParams) { finalResult = finalResult.Replace($"{{{param.Key}}}", param.Value); } // 发送结果给用户 await stepContext.Context.SendActivityAsync(MessageFactory.Text($"搜索结果:{finalResult}"), cancellationToken); // 结束对话 return await stepContext.EndDialogAsync(cancellationToken); } // 辅助方法:模拟从输入提取参数 private Dictionary<string, string> ExtractParamsFromInput(string input) { var paramsDict = new Dictionary<string, string>(); if (input.Contains("北京")) paramsDict.Add("城市", "北京"); if (input.Contains("五星")) paramsDict.Add("星级", "五星"); return paramsDict; }
关键注意事项
- 状态持久化:必须确保
SearchDialogState被正确存入对话状态(如ConversationState),否则每次步骤跳转都会丢失已收集的参数 - 参数识别优化:实际项目中不要用硬编码的参数提取逻辑,建议使用LUIS、Regex或自定义NLP工具提升识别准确率
- 批量询问优化:如果存在多个缺失参数,可以一次性列出所有缺失项,让用户批量输入,减少对话轮次
- 强制跳转:若需要强制回到某个步骤(而非依赖Prompt的自动回调),可以使用
stepContext.ReplaceDialogAsync(nameof(WaterfallDialog), null, cancellationToken)实现
内容的提问来源于stack exchange,提问作者Krumelur
相关产品推荐
相关产品推荐

