markdown-flow 0.2.7__tar.gz → 0.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of markdown-flow might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: An agent library designed to parse and process MarkdownFlow documents
5
5
  Project-URL: Homepage, https://github.com/ai-shifu/markdown-flow-agent-py
6
6
  Project-URL: Bug Tracker, https://github.com/ai-shifu/markdown-flow-agent-py/issues
@@ -83,4 +83,4 @@ __all__ = [
83
83
  "replace_variables_in_text",
84
84
  ]
85
85
 
86
- __version__ = "0.2.7"
86
+ __version__ = "0.2.9"
@@ -492,7 +492,7 @@ class MarkdownFlow:
492
492
 
493
493
  if not matched:
494
494
  if allow_text_input:
495
- # Allow custom text in buttons+text mode
495
+ # Allow custom text in buttons+text mode - same as normal interactions
496
496
  valid_values.append(value)
497
497
  else:
498
498
  invalid_values.append(value)
@@ -1066,47 +1066,102 @@ Analyze the content and provide the structured interaction data.""")
1066
1066
  context: list[dict[str, str]] | None,
1067
1067
  variables: dict[str, str | list[str]] | None,
1068
1068
  ) -> LLMResult:
1069
- """Validate user input for dynamically generated interaction blocks."""
1069
+ """Validate user input for dynamically generated interaction blocks using same logic as normal interactions."""
1070
1070
  _ = block_index # Mark as intentionally unused
1071
- _ = mode # Mark as intentionally unused
1072
1071
  _ = context # Mark as intentionally unused
1073
1072
 
1074
1073
  from .utils import InteractionParser
1075
1074
 
1076
- # Parse the interaction format
1075
+ # Parse the interaction format using the same parser as normal interactions
1077
1076
  parser = InteractionParser()
1078
- interaction = parser.parse(interaction_format)
1077
+ parse_result = parser.parse(interaction_format)
1079
1078
 
1080
- if interaction is None:
1081
- raise ValueError(f"Invalid interaction format: {interaction_format}")
1079
+ if "error" in parse_result:
1080
+ error_msg = f"Invalid interaction format: {parse_result['error']}"
1081
+ return self._render_error(error_msg, mode)
1082
1082
 
1083
- # Extract variable name from the interaction format
1084
- # This is a simplified extraction - in real implementation you'd use the parser result
1085
- import re
1083
+ # Extract variable name and interaction type
1084
+ variable_name = parse_result.get("variable")
1085
+ interaction_type = parse_result.get("type")
1086
1086
 
1087
- var_match = re.search(r"%\{\{([^}]+)\}\}", interaction_format)
1088
- if not var_match:
1089
- raise ValueError(f"No variable found in interaction format: {interaction_format}")
1087
+ if not variable_name:
1088
+ error_msg = f"No variable found in interaction format: {interaction_format}"
1089
+ return self._render_error(error_msg, mode)
1090
1090
 
1091
- variable_name = var_match.group(1)
1091
+ # Get user input for the target variable
1092
+ target_values = user_input.get(variable_name, [])
1092
1093
 
1093
- # Validate the user input
1094
- user_values = user_input.get(variable_name, [])
1095
- if not user_values:
1096
- raise ValueError(f"No input provided for variable: {variable_name}")
1094
+ # Basic validation - check if input is provided when required
1095
+ if not target_values:
1096
+ # Check if this is a text input or allows empty input
1097
+ allow_text_input = interaction_type in [
1098
+ InteractionType.BUTTONS_WITH_TEXT,
1099
+ InteractionType.BUTTONS_MULTI_WITH_TEXT,
1100
+ ]
1097
1101
 
1098
- # Process the validation result
1099
- updated_variables = dict(variables or {})
1102
+ if allow_text_input:
1103
+ # Allow empty input for buttons+text mode - merge with existing variables
1104
+ merged_variables = dict(variables or {})
1105
+ merged_variables[variable_name] = []
1106
+ return LLMResult(
1107
+ content="",
1108
+ variables=merged_variables,
1109
+ metadata={
1110
+ "interaction_type": "dynamic_interaction",
1111
+ "empty_input": True,
1112
+ },
1113
+ )
1114
+ else:
1115
+ error_msg = f"No input provided for variable '{variable_name}'"
1116
+ return self._render_error(error_msg, mode)
1100
1117
 
1101
- # Handle single vs multiple values
1102
- if len(user_values) == 1:
1103
- updated_variables[variable_name] = user_values[0]
1104
- else:
1105
- updated_variables[variable_name] = user_values
1118
+ # Use the same validation logic as normal interactions
1119
+ if interaction_type in [
1120
+ InteractionType.BUTTONS_ONLY,
1121
+ InteractionType.BUTTONS_WITH_TEXT,
1122
+ InteractionType.BUTTONS_MULTI_SELECT,
1123
+ InteractionType.BUTTONS_MULTI_WITH_TEXT,
1124
+ ]:
1125
+ # Button validation - reuse the existing button validation logic
1126
+ button_result = self._process_button_validation(
1127
+ parse_result,
1128
+ target_values,
1129
+ variable_name,
1130
+ mode,
1131
+ interaction_type,
1132
+ )
1106
1133
 
1107
- # Return successful validation result
1108
- return LLMResult(
1109
- content=f"Successfully collected {variable_name}: {user_values}",
1110
- variables=updated_variables,
1111
- metadata={"validation_success": True, "variable_collected": variable_name, "values_collected": user_values},
1112
- )
1134
+ # Merge with existing variables for dynamic interactions
1135
+ if hasattr(button_result, 'variables') and button_result.variables is not None and variables:
1136
+ merged_variables = dict(variables)
1137
+ merged_variables.update(button_result.variables)
1138
+ return LLMResult(
1139
+ content=button_result.content,
1140
+ variables=merged_variables,
1141
+ metadata=button_result.metadata,
1142
+ )
1143
+ return button_result
1144
+
1145
+ elif interaction_type == InteractionType.NON_ASSIGNMENT_BUTTON:
1146
+ # Non-assignment buttons: don't set variables, keep existing ones
1147
+ return LLMResult(
1148
+ content="",
1149
+ variables=dict(variables or {}),
1150
+ metadata={
1151
+ "interaction_type": "non_assignment_button",
1152
+ "user_input": user_input,
1153
+ },
1154
+ )
1155
+ else:
1156
+ # Text-only input type - merge with existing variables
1157
+ merged_variables = dict(variables or {})
1158
+ merged_variables[variable_name] = target_values
1159
+ return LLMResult(
1160
+ content="",
1161
+ variables=merged_variables,
1162
+ metadata={
1163
+ "interaction_type": "text_only",
1164
+ "target_variable": variable_name,
1165
+ "values": target_values,
1166
+ },
1167
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: An agent library designed to parse and process MarkdownFlow documents
5
5
  Project-URL: Homepage, https://github.com/ai-shifu/markdown-flow-agent-py
6
6
  Project-URL: Bug Tracker, https://github.com/ai-shifu/markdown-flow-agent-py/issues
@@ -49,20 +49,66 @@ def test_chinese_restaurant_scenario():
49
49
  print(f"生成的交互格式: {result1.content}")
50
50
 
51
51
  if result1.transformed_to_interaction:
52
- # 第二步:用户选择菜品
53
- print("\n--- 用户选择菜品 ---")
52
+ # 验证交互格式正确性
53
+ print("\n--- 验证交互格式 ---")
54
+ assert "?[" in result1.content, "交互格式应该包含 ?["
55
+ assert "%{{菜品选择}}" in result1.content, "应该包含变量名"
56
+ print("✅ 交互格式验证通过")
57
+
58
+ # 验证是否为多选格式
59
+ is_multi_select = "||" in result1.content
60
+ print(f"多选格式: {is_multi_select}")
61
+ if is_multi_select:
62
+ print("✅ 正确识别为多选场景")
63
+
64
+ # 第二步:测试有效选择
65
+ print("\n--- 测试有效选择 ---")
54
66
  user_choices = ["宫保鸡丁", "麻婆豆腐"] # 模拟用户多选
55
-
56
67
  result2 = mf.process(
57
68
  block_index=0,
58
69
  mode=ProcessMode.COMPLETE,
59
70
  user_input={"菜品选择": user_choices},
60
71
  dynamic_interaction_format=result1.content
61
72
  )
62
-
63
73
  print(f"用户选择: {user_choices}")
64
74
  print(f"验证后的变量: {result2.variables}")
65
- print(" 中文餐厅场景测试完成")
75
+ assert result2.variables.get("菜品选择") == user_choices, "变量应该正确存储用户选择"
76
+ print("✅ 有效选择验证通过")
77
+
78
+ # 第三步:测试无效选择
79
+ print("\n--- 测试无效选择 ---")
80
+ try:
81
+ invalid_choices = ["意大利面", "汉堡包"] # 不在选项中的选择
82
+ result3 = mf.process(
83
+ block_index=0,
84
+ mode=ProcessMode.COMPLETE,
85
+ user_input={"菜品选择": invalid_choices},
86
+ dynamic_interaction_format=result1.content
87
+ )
88
+ if hasattr(result3, 'content') and result3.content and "Invalid" in result3.content:
89
+ print("✅ 正确处理无效选择")
90
+ else:
91
+ print("⚠️ 可能允许了无效选择")
92
+ except Exception as e:
93
+ print(f"✅ 正确拒绝无效选择: {str(e)[:50]}...")
94
+
95
+ # 第四步:测试空输入
96
+ print("\n--- 测试空输入 ---")
97
+ try:
98
+ result4 = mf.process(
99
+ block_index=0,
100
+ mode=ProcessMode.COMPLETE,
101
+ user_input={"菜品选择": []},
102
+ dynamic_interaction_format=result1.content
103
+ )
104
+ if hasattr(result4, 'content') and result4.content:
105
+ print("⚠️ 空输入可能被接受(取决于交互类型)")
106
+ else:
107
+ print("✅ 正确处理空输入")
108
+ except Exception as e:
109
+ print(f"✅ 正确处理空输入错误: {str(e)[:50]}...")
110
+
111
+ print("✅ 中文餐厅场景完整验证通过")
66
112
 
67
113
  except Exception as e:
68
114
  print(f"❌ 测试失败: {e}")
@@ -101,19 +147,61 @@ Format: Provide specific educational options"""
101
147
  print(f"Generated interaction format: {result1.content}")
102
148
 
103
149
  if result1.transformed_to_interaction:
104
- # 第二步:用户选择学习选项
105
- print("\n--- User Makes Selection ---")
106
- user_choices = ["Computer Science", "Online"] # 模拟用户选择
150
+ # 验证交互格式正确性
151
+ print("\n--- Validate Interaction Format ---")
152
+ assert "?[" in result1.content, "Should contain ?["
153
+ assert "%{{learning_choice}}" in result1.content, "Should contain variable name"
154
+ print("✅ Interaction format validated")
155
+
156
+ # 验证英文选项内容
157
+ has_english_content = any(word in result1.content for word in ["Computer", "Science", "Online", "Business"])
158
+ if has_english_content:
159
+ print("✅ Generated English content as requested")
160
+ else:
161
+ print("⚠️ May not contain expected English content")
107
162
 
163
+ # 第二步:测试有效选择
164
+ print("\n--- Test Valid Selection ---")
165
+ user_choices = ["Computer Science", "Online"] # 模拟用户选择
108
166
  result2 = mf.process(
109
167
  block_index=0,
110
168
  mode=ProcessMode.COMPLETE,
111
169
  user_input={"learning_choice": user_choices},
112
170
  dynamic_interaction_format=result1.content
113
171
  )
114
-
115
172
  print(f"User choices: {user_choices}")
116
173
  print(f"Validated variables: {result2.variables}")
174
+ assert result2.variables.get("learning_choice") == user_choices, "Variables should store user selection"
175
+ print("✅ Valid selection validated")
176
+
177
+ # 第三步:测试部分匹配选择
178
+ print("\n--- Test Partial Match ---")
179
+ try:
180
+ partial_choices = ["Computer"] # 只选择一个词
181
+ result3 = mf.process(
182
+ block_index=0,
183
+ mode=ProcessMode.COMPLETE,
184
+ user_input={"learning_choice": partial_choices},
185
+ dynamic_interaction_format=result1.content
186
+ )
187
+ print(f"Partial selection: {partial_choices}")
188
+ print(f"Result: {result3.variables}")
189
+ except Exception as e:
190
+ print(f"Partial selection handling: {str(e)[:50]}...")
191
+
192
+ # 第四步:测试单个选择
193
+ print("\n--- Test Single Selection ---")
194
+ single_choice = ["Business"]
195
+ result4 = mf.process(
196
+ block_index=0,
197
+ mode=ProcessMode.COMPLETE,
198
+ user_input={"learning_choice": single_choice},
199
+ dynamic_interaction_format=result1.content
200
+ )
201
+ expected_value = single_choice[0] if len(single_choice) == 1 else single_choice
202
+ actual_value = result4.variables.get("learning_choice")
203
+ print(f"Single choice: {single_choice}")
204
+ print(f"Stored as: {actual_value}")
117
205
  print("✅ English education scenario completed")
118
206
 
119
207
  except Exception as e:
@@ -153,6 +241,14 @@ def test_japanese_fitness_scenario():
153
241
  print(f"生成されたインタラクション形式: {result1.content}")
154
242
 
155
243
  if result1.transformed_to_interaction:
244
+ # 验证日文内容
245
+ japanese_exercises = ["ランニング", "水泳", "ヨガ", "ウェイト"]
246
+ has_japanese_content = any(exercise in result1.content for exercise in japanese_exercises)
247
+ if has_japanese_content:
248
+ print("✅ 生成了日文运动选项")
249
+ else:
250
+ print("⚠️ 可能未生成预期的日文内容")
251
+
156
252
  # 第二步:用户选择运动
157
253
  print("\n--- ユーザー選択 ---")
158
254
  user_choices = ["ランニング", "ヨガ"] # 模拟用户选择
@@ -166,6 +262,7 @@ def test_japanese_fitness_scenario():
166
262
 
167
263
  print(f"ユーザー選択: {user_choices}")
168
264
  print(f"検証後の変数: {result2.variables}")
265
+ assert result2.variables.get("運動選択") == user_choices, "变量应该正确存储"
169
266
  print("✅ 日本語フィットネスシナリオ完了")
170
267
 
171
268
  except Exception as e:
@@ -205,6 +302,14 @@ def test_korean_travel_scenario():
205
302
  print(f"생성된 인터랙션 형식: {result1.content}")
206
303
 
207
304
  if result1.transformed_to_interaction:
305
+ # 验证韩文内容
306
+ korean_travel = ["휴양", "문화탐방", "호텔", "펜션"]
307
+ has_korean_content = any(option in result1.content for option in korean_travel)
308
+ if has_korean_content:
309
+ print("✅ 생성된 한국어 여행 옵션")
310
+ else:
311
+ print("⚠️ 예상된 한국어 콘텐츠가 생성되지 않았을 수 있음")
312
+
208
313
  # 第二步:用户选择旅游选项
209
314
  print("\n--- 사용자 선택 ---")
210
315
  user_choices = ["문화탐방", "호텔"] # 模拟用户选择
@@ -218,6 +323,7 @@ def test_korean_travel_scenario():
218
323
 
219
324
  print(f"사용자 선택: {user_choices}")
220
325
  print(f"검증된 변수: {result2.variables}")
326
+ assert result2.variables.get("여행선택") == user_choices, "변수가 올바르게 저장되어야 함"
221
327
  print("✅ 한국어 여행 시나리오 완료")
222
328
 
223
329
  except Exception as e:
@@ -279,6 +385,15 @@ def test_complex_job_consultation_scenario():
279
385
  print(f"职位选项: {result2.content}")
280
386
 
281
387
  if result2.transformed_to_interaction:
388
+ # 验证职位选项是否基于行业生成
389
+ print("\n--- 验证职位选项上下文相关性 ---")
390
+ tech_jobs = ["软件工程师", "数据科学家", "产品经理", "工程师"]
391
+ has_tech_jobs = any(job in result2.content for job in tech_jobs)
392
+ if has_tech_jobs:
393
+ print("✅ 正确基于'科技'行业生成了相关职位")
394
+ else:
395
+ print("⚠️ 可能未正确基于行业上下文生成职位")
396
+
282
397
  # 第三步:用户选择职位
283
398
  print("\n--- 步骤3: 用户选择职位 ---")
284
399
  job_choices = ["软件工程师", "数据科学家"]
@@ -292,6 +407,28 @@ def test_complex_job_consultation_scenario():
292
407
  print(f"用户选择职位: {job_choices}")
293
408
  print(f"第二步变量: {result3.variables}")
294
409
 
410
+ # 验证职位选择验证
411
+ assert result3.variables.get("职位选择") == job_choices, "职位变量应该正确存储"
412
+ assert result3.variables.get("行业") == ["科技"], "行业变量应该保持不变"
413
+ print("✅ 职位选择验证通过")
414
+
415
+ # 测试无效职位选择
416
+ print("\n--- 测试无效职位选择 ---")
417
+ try:
418
+ invalid_job = ["厨师", "司机"] # 不属于科技行业的职位
419
+ result_invalid = mf.process(
420
+ block_index=1,
421
+ mode=ProcessMode.COMPLETE,
422
+ variables=result1.variables,
423
+ user_input={"职位选择": invalid_job},
424
+ dynamic_interaction_format=result2.content
425
+ )
426
+ print("⚠️ 无效职位选择可能被接受")
427
+ # 避免未使用变量警告
428
+ _ = result_invalid
429
+ except Exception as e:
430
+ print(f"✅ 正确拒绝无效职位选择: {str(e)[:50]}...")
431
+
295
432
  # 第四步:生成薪资选项
296
433
  print("\n--- 步骤4: 生成薪资选项 ---")
297
434
  result4 = mf.process(
@@ -303,6 +440,17 @@ def test_complex_job_consultation_scenario():
303
440
  print(f"薪资选项: {result4.content}")
304
441
 
305
442
  if result4.transformed_to_interaction:
443
+ # 验证薪资选项格式
444
+ salary_ranges = ["5-10万", "10-20万", "20-30万", "30万"]
445
+ has_salary_ranges = any(salary in result4.content for salary in salary_ranges)
446
+ if has_salary_ranges:
447
+ print("✅ 生成了预期的薪资范围选项")
448
+
449
+ # 验证是否为单选(薪资期望通常是单选)
450
+ is_single_select = "||" not in result4.content and "|" in result4.content
451
+ if is_single_select:
452
+ print("✅ 正确识别薪资选择为单选模式")
453
+
306
454
  # 第五步:用户选择薪资期望
307
455
  print("\n--- 步骤5: 用户选择薪资期望 ---")
308
456
  salary_choice = ["20-30万"]
@@ -315,7 +463,15 @@ def test_complex_job_consultation_scenario():
315
463
  )
316
464
  print(f"用户选择薪资: {salary_choice}")
317
465
  print(f"最终变量: {result5.variables}")
318
- print("✅ 复杂职业咨询场景完成")
466
+
467
+ # 验证最终变量完整性
468
+ expected_vars = ["行业", "职位选择", "薪资期望"]
469
+ for var in expected_vars:
470
+ assert var in result5.variables, f"最终结果应该包含变量: {var}"
471
+
472
+ # 避免未使用变量警告
473
+ _ = result5
474
+ print("✅ 复杂职业咨询场景完整验证通过")
319
475
 
320
476
  except Exception as e:
321
477
  print(f"❌ 测试失败: {e}")
@@ -353,8 +509,22 @@ def test_text_input_scenario():
353
509
  print(f"生成的选项: {result1.content}")
354
510
 
355
511
  if result1.transformed_to_interaction:
512
+ # 验证混合输入格式(按钮+文本)
513
+ print("\n--- 验证混合输入格式 ---")
514
+ has_buttons = "|" in result1.content
515
+ has_text_input = "..." in result1.content
516
+ print(f"包含按钮: {has_buttons}")
517
+ print(f"包含文本输入: {has_text_input}")
518
+
519
+ if has_buttons and has_text_input:
520
+ print("✅ 正确生成混合输入格式(按钮+文本)")
521
+ elif has_buttons:
522
+ print("⚠️ 只有按钮选项,没有文本输入选项")
523
+ else:
524
+ print("⚠️ 格式可能不符合预期")
525
+
356
526
  # 测试预设选项选择
357
- print("\n--- 用户选择预设选项 ---")
527
+ print("\n--- 测试预设按钮选择 ---")
358
528
  preset_choices = ["定制颜色", "个性化logo"]
359
529
  result2 = mf.process(
360
530
  block_index=0,
@@ -364,9 +534,11 @@ def test_text_input_scenario():
364
534
  )
365
535
  print(f"预设选项: {preset_choices}")
366
536
  print(f"验证后的变量: {result2.variables}")
537
+ assert result2.variables.get("自定义需求") == preset_choices, "应该正确存储预设选项"
538
+ print("✅ 预设选项验证通过")
367
539
 
368
- # 测试文本输入
369
- print("\n--- 用户自定义文本输入 ---")
540
+ # 测试自定义文本输入
541
+ print("\n--- 测试自定义文本输入 ---")
370
542
  custom_input = ["需要特殊的防水涂层处理"]
371
543
  result3 = mf.process(
372
544
  block_index=0,
@@ -376,7 +548,37 @@ def test_text_input_scenario():
376
548
  )
377
549
  print(f"自定义输入: {custom_input}")
378
550
  print(f"验证后的变量: {result3.variables}")
379
- print(" 文本输入场景测试完成")
551
+ assert result3.variables.get("自定义需求") == custom_input, "应该正确存储自定义文本"
552
+ print("✅ 自定义文本验证通过")
553
+
554
+ # 测试混合选择(按钮+自定义)
555
+ print("\n--- 测试混合选择 ---")
556
+ mixed_input = ["定制颜色", "需要增加夜光效果"] # 一个按钮选项+一个自定义
557
+ result4 = mf.process(
558
+ block_index=0,
559
+ mode=ProcessMode.COMPLETE,
560
+ user_input={"自定义需求": mixed_input},
561
+ dynamic_interaction_format=result1.content
562
+ )
563
+ print(f"混合输入: {mixed_input}")
564
+ print(f"验证后的变量: {result4.variables}")
565
+ assert result4.variables.get("自定义需求") == mixed_input, "应该正确存储混合输入"
566
+ print("✅ 混合选择验证通过")
567
+
568
+ # 测试空输入(对于支持文本输入的交互,可能允许空输入)
569
+ print("\n--- 测试空输入处理 ---")
570
+ try:
571
+ result5 = mf.process(
572
+ block_index=0,
573
+ mode=ProcessMode.COMPLETE,
574
+ user_input={"自定义需求": []},
575
+ dynamic_interaction_format=result1.content
576
+ )
577
+ print("✅ 空输入被接受(支持文本输入的交互可能允许空输入)")
578
+ except Exception as e:
579
+ print(f"空输入被拒绝: {str(e)[:50]}...")
580
+
581
+ print("✅ 文本输入场景完整验证通过")
380
582
 
381
583
  except Exception as e:
382
584
  print(f"❌ 测试失败: {e}")
@@ -433,6 +635,7 @@ def test_variable_context_cuisine_scenario():
433
635
 
434
636
  # 验证是否生成了川菜相关的选项
435
637
  if result2.transformed_to_interaction:
638
+ print("\n--- 验证上下文相关性 ---")
436
639
  sichuan_dishes = ['宫保鸡丁', '麻婆豆腐', '水煮鱼', '回锅肉']
437
640
  has_sichuan_dishes = any(dish in result2.content for dish in sichuan_dishes)
438
641
 
@@ -442,24 +645,78 @@ def test_variable_context_cuisine_scenario():
442
645
  print("⚠️ 可能未正确基于菜系上下文生成选项")
443
646
 
444
647
  # 验证是否使用了多选格式
445
- if "||" in result2.content:
446
- print("✅ 正确识别为多选场景(用户可以点多个菜品)")
648
+ is_multi_select = "||" in result2.content
649
+ if is_multi_select:
650
+ print("✅ 正确识别为多选场景(用户可以选多个菜品)")
447
651
  else:
448
652
  print("⚠️ 可能未正确识别为多选场景")
449
653
 
654
+ # 验证变量名正确性
655
+ assert "%{{菜品}}" in result2.content, "应该包含正确的变量名"
656
+ print("✅ 变量名验证通过")
657
+
450
658
  # 第三步:用户选择菜品
451
659
  print("\n--- 步骤3: 用户选择菜品 ---")
660
+ dish_choices = ["宫保鸡丁", "麻婆豆腐"]
452
661
  result3 = mf.process(
453
662
  block_index=1,
454
663
  mode=ProcessMode.COMPLETE,
455
664
  variables=result1.variables,
456
- user_input={"菜品": ["宫保鸡丁", "麻婆豆腐"]},
665
+ user_input={"菜品": dish_choices},
457
666
  dynamic_interaction_format=result2.content
458
667
  )
459
668
 
460
- print(f"用户选择菜品: ['宫保鸡丁', '麻婆豆腐']")
669
+ print(f"用户选择菜品: {dish_choices}")
461
670
  print(f"最终变量: {result3.variables}")
462
- print("✅ 变量上下文场景测试完成")
671
+
672
+ # 验证变量存储正确性
673
+ cuisine_var = result3.variables.get("菜系")
674
+ dish_var = result3.variables.get("菜品")
675
+
676
+ print(f"菜系变量: {cuisine_var}")
677
+ print(f"菜品变量: {dish_var}")
678
+
679
+ # 更宽松的验证 - 菜系可能是字符串或列表
680
+ if cuisine_var == ["川菜"] or cuisine_var == "川菜":
681
+ print("✅ 菜系变量正确保留")
682
+ else:
683
+ print(f"⚠️ 菜系变量格式不符合预期: {cuisine_var}")
684
+
685
+ assert dish_var == dish_choices, "菜品变量应该正确存储"
686
+ assert "菜系" in result3.variables, "应该包含菜系变量"
687
+ assert "菜品" in result3.variables, "应该包含菜品变量"
688
+ print("✅ 变量存储验证通过")
689
+
690
+ # 测试单个菜品选择
691
+ print("\n--- 测试单个菜品选择 ---")
692
+ single_dish = ["宫保鸡丁"]
693
+ result4 = mf.process(
694
+ block_index=1,
695
+ mode=ProcessMode.COMPLETE,
696
+ variables=result1.variables,
697
+ user_input={"菜品": single_dish},
698
+ dynamic_interaction_format=result2.content
699
+ )
700
+ print(f"单个选择结果: {result4.variables.get('菜品')}")
701
+
702
+ # 测试无效菜品选择
703
+ print("\n--- 测试无效菜品选择 ---")
704
+ try:
705
+ invalid_dishes = ["北京烤鸭", "小笼包"] # 非川菜选项
706
+ result_invalid = mf.process(
707
+ block_index=1,
708
+ mode=ProcessMode.COMPLETE,
709
+ variables=result1.variables,
710
+ user_input={"菜品": invalid_dishes},
711
+ dynamic_interaction_format=result2.content
712
+ )
713
+ print("⚠️ 无效菜品选择可能被接受")
714
+ # 避免未使用变量警告
715
+ _ = result_invalid
716
+ except Exception as e:
717
+ print(f"✅ 正确拒绝无效菜品选择: {str(e)[:50]}...")
718
+
719
+ print("✅ 变量上下文场景完整验证通过")
463
720
 
464
721
  except Exception as e:
465
722
  print(f"❌ 测试失败: {e}")
@@ -515,6 +772,7 @@ Format: Provide specific project options based on the selected programming langu
515
772
 
516
773
  if result2.transformed_to_interaction:
517
774
  # 验证是否生成了Python相关的项目
775
+ print("\n--- Validate Context-Based Project Options ---")
518
776
  python_projects = ['scraping', 'analysis', 'learning', 'Django']
519
777
  has_python_projects = any(project.lower() in result2.content.lower() for project in python_projects)
520
778
 
@@ -523,24 +781,133 @@ Format: Provide specific project options based on the selected programming langu
523
781
  else:
524
782
  print("⚠️ May not have correctly generated context-based options")
525
783
 
784
+ # 验证多选格式(项目通常可以多选)
785
+ is_multi_select = "||" in result2.content
786
+ if is_multi_select:
787
+ print("✅ Correctly identified as multi-select scenario")
788
+
526
789
  # 第三步:用户选择项目
527
790
  print("\n--- Step 3: User selects projects ---")
791
+ project_choices = ["Data analysis", "Machine learning"]
528
792
  result3 = mf.process(
529
793
  block_index=1,
530
794
  mode=ProcessMode.COMPLETE,
531
795
  variables=result1.variables,
532
- user_input={"project_type": ["Data analysis", "Machine learning"]},
796
+ user_input={"project_type": project_choices},
533
797
  dynamic_interaction_format=result2.content
534
798
  )
535
799
 
536
- print(f"User project selection: ['Data analysis', 'Machine learning']")
800
+ print(f"User project selection: {project_choices}")
537
801
  print(f"Final variables: {result3.variables}")
538
- print("✅ Skill-project scenario completed")
802
+
803
+ # 验证变量存储
804
+ assert result3.variables.get("skill") == ["Python"], "Skill should remain unchanged"
805
+ assert result3.variables.get("project_type") == project_choices, "Projects should be stored correctly"
806
+ print("✅ Variable storage validated")
807
+
808
+ # 测试单个项目选择
809
+ print("\n--- Test Single Project Selection ---")
810
+ single_project = ["Web scraping"]
811
+ result4 = mf.process(
812
+ block_index=1,
813
+ mode=ProcessMode.COMPLETE,
814
+ variables=result1.variables,
815
+ user_input={"project_type": single_project},
816
+ dynamic_interaction_format=result2.content
817
+ )
818
+ print(f"Single project result: {result4.variables.get('project_type')}")
819
+
820
+ print("✅ Skill-project scenario validation completed")
539
821
 
540
822
  except Exception as e:
541
823
  print(f"❌ Test failed: {e}")
542
824
 
543
825
 
826
+ def test_user_question_text_input():
827
+ """测试用户疑问式文本输入场景"""
828
+ print("\n=== 用户疑问文本输入测试 ===")
829
+
830
+ document = """询问用户的故事风格偏好,并记录到变量{{风格选择}}"""
831
+
832
+ document_prompt = """你是故事创作助手。为用户提供故事风格选项:
833
+ - 常见风格:幽默、搞笑、悬疑、浪漫、文言文
834
+ - 同时允许用户输入其他风格偏好
835
+
836
+ 语言:中文
837
+ 格式:提供常见风格选项 + 允许自定义文本输入(使用...前缀)"""
838
+
839
+ try:
840
+ llm_provider = create_llm_provider()
841
+ mf = MarkdownFlow(
842
+ document=document,
843
+ llm_provider=llm_provider,
844
+ document_prompt=document_prompt
845
+ )
846
+
847
+ # 生成动态交互
848
+ print("--- 生成故事风格选项 ---")
849
+ result1 = mf.process(
850
+ block_index=0,
851
+ mode=ProcessMode.COMPLETE
852
+ )
853
+
854
+ print(f"转换为交互块: {result1.transformed_to_interaction}")
855
+ print(f"生成的交互: {result1.content}")
856
+
857
+ if result1.transformed_to_interaction:
858
+ # 验证是否包含文本输入选项
859
+ has_text_input = "..." in result1.content
860
+ if has_text_input:
861
+ print("✅ 包含文本输入选项")
862
+
863
+ # 测试用户疑问式输入
864
+ print("\n--- 测试疑问式文本输入 ---")
865
+ question_input = ["这里必须要选择么?"]
866
+
867
+ result2 = mf.process(
868
+ block_index=0,
869
+ mode=ProcessMode.COMPLETE,
870
+ user_input={"风格选择": question_input},
871
+ dynamic_interaction_format=result1.content
872
+ )
873
+
874
+ print(f"用户疑问输入: {question_input}")
875
+ print(f"验证结果: {result2.variables}")
876
+
877
+ # 验证疑问输入的处理
878
+ result_value = result2.variables.get("风格选择")
879
+ if result_value == question_input or result_value == question_input[0]:
880
+ print("✅ 疑问式文本输入被接受(后续需LLM处理语义)")
881
+ print("📝 注意:语义验证应由LLM负责,固定代码只做格式验证")
882
+ else:
883
+ print(f"⚠️ 输入处理结果不符合预期: {result_value}")
884
+
885
+ # 测试其他类型的自定义输入
886
+ print("\n--- 测试其他自定义输入 ---")
887
+ custom_inputs = [
888
+ ["我想要科幻加悬疑的混合风格"],
889
+ ["可以不选择吗"],
890
+ ["这些选项都不适合我"]
891
+ ]
892
+
893
+ for custom_input in custom_inputs:
894
+ try:
895
+ result_custom = mf.process(
896
+ block_index=0,
897
+ mode=ProcessMode.COMPLETE,
898
+ user_input={"风格选择": custom_input},
899
+ dynamic_interaction_format=result1.content
900
+ )
901
+ print(f"自定义输入 '{custom_input[0]}' - 结果: {result_custom.variables.get('风格选择')}")
902
+ except Exception as e:
903
+ print(f"自定义输入 '{custom_input[0]}' - 错误: {str(e)[:50]}...")
904
+
905
+ print("✅ 用户疑问文本输入场景测试完成")
906
+
907
+ except Exception as e:
908
+ print(f"❌ 测试失败: {e}")
909
+
910
+
544
911
  def run_all_tests():
545
912
  """运行所有 document_prompt 测试"""
546
913
  print("🧪 开始 document_prompt 动态交互测试")
File without changes
File without changes
File without changes