markdown-flow 0.2.30__py3-none-any.whl → 0.2.31__py3-none-any.whl

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.
markdown_flow/__init__.py CHANGED
@@ -82,5 +82,5 @@ __all__ = [
82
82
  "replace_variables_in_text",
83
83
  ]
84
84
 
85
- __version__ = "0.2.30"
86
- # __version__ = "0.2.29-alpha-1"
85
+ __version__ = "0.2.31"
86
+ # __version__ = "0.2.30-alpha-1"
@@ -26,12 +26,6 @@ COMPILED_LAYER3_BUTTON_VALUE_REGEX = re.compile(r"^(.+)//(.+)$") # Layer 3: Par
26
26
  COMPILED_BRACE_VARIABLE_REGEX = re.compile(
27
27
  r"(?<!%)\{\{([^}]+)\}\}" # Match {{variable}} format for replaceable variables
28
28
  )
29
- COMPILED_INTERACTION_CONTENT_RECONSTRUCT_REGEX = re.compile(
30
- r"(\?\[[^]]*\.\.\.)([^]]*\])" # Reconstruct interaction content: prefix + question + suffix
31
- )
32
- COMPILED_BRACKETS_CLEANUP_REGEX = re.compile(r"[\[\]()]")
33
- COMPILED_VARIABLE_REFERENCE_CLEANUP_REGEX = re.compile(r"%\{\{[^}]*\}\}")
34
- COMPILED_WHITESPACE_CLEANUP_REGEX = re.compile(r"\s+")
35
29
  COMPILED_SINGLE_PIPE_SPLIT_REGEX = re.compile(r"(?<!\|)\|(?!\|)") # Split on single | but not ||
36
30
 
37
31
  # Document parsing constants (using shared INTERACTION_PATTERN defined above)
@@ -152,28 +146,6 @@ DEFAULT_INTERACTION_PROMPT = """<interaction_translation_rules>
152
146
  # Interaction error prompt templates
153
147
  DEFAULT_INTERACTION_ERROR_PROMPT = "请将以下错误信息改写得更加友好和个性化,帮助用户理解问题并给出建设性的引导:"
154
148
 
155
- # Detailed interaction rendering instructions
156
- INTERACTION_RENDER_INSTRUCTIONS = """
157
- 核心要求:
158
- 1. **绝对禁止改变问题的含义和方向** - 这是最重要的原则
159
- 2. 只能改变表达方式,不能改变问题的核心内容
160
- 3. 必须保持问题的主体和客体关系不变
161
- 4. 只返回改写后的问题文本,不要包含任何其他内容
162
- 5. 保持专业友好的语气,禁止可爱化表达
163
-
164
- 关键示例说明:
165
- ✅ 正确改写(保持含义):
166
- - "希望我怎么称呼你?" → "请问我应该如何称呼您?"
167
- - "请输入您的姓名" → "请告诉我您的姓名"
168
- - "你的年龄是多少?" → "请问您今年多大了?"
169
-
170
- ❌ 严重错误(改变含义):
171
- - "希望我怎么称呼你?" → "你想叫我什么名字?" (方向颠倒)
172
- - "请输入您的姓名" → "我叫什么好呢?" (主客体颠倒)
173
- - "你喜欢什么?" → "我应该喜欢什么?" (完全改变意思)
174
-
175
- 请严格按照以上要求改写,确保不改变问题的原始含义:"""
176
-
177
149
  # Interaction error rendering instructions
178
150
  INTERACTION_ERROR_RENDER_INSTRUCTIONS = """
179
151
  请只返回友好的错误提示,不要包含其他格式或说明。"""
@@ -284,25 +256,63 @@ OUTPUT_INSTRUCTION_EXPLANATION = f"""<preserve_or_translate_instruction>
284
256
  """
285
257
 
286
258
  # Validation task template (merged with system message)
287
- VALIDATION_TASK_TEMPLATE = """你是一个验证用户输入的助手,请严格按照给定的指令进行验证。
288
-
289
- # 任务
290
- 从用户回答中提取相关信息,返回JSON格式结果:
291
- - 合法:{{"result": "ok", "parse_vars": {{"{target_variable}": "提取的内容"}}}}
292
- - 不合法:{{"result": "illegal", "reason": "原因"}}
293
-
294
- # 输出语言
295
- - 如果在 <document_context> 中明确要求使用特定语言,则错误信息和原因说明应使用该语言
296
- - 否则,使用用户输入或问题描述的主要语言"""
297
-
298
- # Validation requirements template (lenient general version)
299
- VALIDATION_REQUIREMENTS_TEMPLATE = """# 提取要求
300
- 1. 仔细阅读上述相关问题,理解这个问题想要获取什么信息
301
- 2. 从用户回答中提取与该问题相关的信息
302
- 3. 如果提供了预定义选项,用户选择这些选项时都应该接受;自定义输入只要是对问题的合理回答即可接受
303
- 4. 对于昵称、姓名、标签等自由文本输入,任何非空的合理表达都应该接受(包括数字、字母、符号、emoji等创意性表达)
304
- 5. 只有当用户回答完全无关、包含不当内容或明显违背常识时才标记为不合法
305
- 6. 宽松验证原则:理解用户意图,接受多样化的合理表达形式"""
259
+ VALIDATION_TASK_TEMPLATE = """你是字符串验证程序,不是对话助手。
260
+
261
+ 你的唯一任务:按后续规则检查输入,输出 JSON:
262
+ {{"result": "ok", "parse_vars": {{"{target_variable}": "用户输入"}}}} 或 {{"result": "illegal", "reason": "原因"}}
263
+
264
+ 严禁输出任何自然语言解释。
265
+
266
+ # reason 语言
267
+ <document_context> 中仅提取语言要求(如"使用英文"、"use English")
268
+ - 如果有明确语言要求 → reason 使用该语言
269
+ - 否则 → reason 使用用户输入或问题的语言
270
+ """
271
+
272
+ # Validation requirements template (极致宽松版本)
273
+ VALIDATION_REQUIREMENTS_TEMPLATE = """# 验证算法(按顺序执行)
274
+
275
+ 步骤 1:空值检查(字符串长度检查)
276
+
277
+ 检查规则:input.trim().length == 0 ?
278
+ - YES → 空
279
+ - NO → 非空
280
+
281
+ ⚠️ 只要去除首尾空格后字符数 > 0,就是非空
282
+ ⚠️ 不判断语义!所有可见字符(a、1、@、中)都计入长度
283
+ ⚠️ 示例:
284
+ - "" → 长度0 → 空
285
+ - " " → 长度0 → 空
286
+ - "aa" → 长度2 → 非空
287
+ - "@_@" → 长度3 → 非空
288
+ - "棒棒糖" → 长度3 → 非空
289
+
290
+ 步骤 2:模糊回答检查
291
+
292
+ 拒绝以下模糊回答:"不知道"、"不清楚"、"没有"、"不告诉你"
293
+
294
+ 步骤 3:宗教政治检查
295
+
296
+ 只拒绝明确的宗教政治立场表达(宗教教义、政治口号等)
297
+ 地名,地区等(北京、上海等)、普通词汇都不算
298
+
299
+ 步骤 4:输出结果(reason 语言跟随 <document_context> 中的语言要求)
300
+
301
+ 伪代码逻辑:
302
+ if 空:
303
+ 输出 {{"result": "illegal", "reason": "输入为空(或对应语言的翻译)"}}
304
+ else if 模糊回答:
305
+ 输出 {{"result": "illegal", "reason": "请提供具体内容(或对应语言的翻译)"}}
306
+ else if 宗教政治:
307
+ 输出 {{"result": "illegal", "reason": "包含敏感内容(或对应语言的翻译)"}}
308
+ else:
309
+ 输出 {{"result": "ok", "parse_vars": {{"{target_variable}": "用户输入"}}}}
310
+
311
+ ⚠️ 极致重要:
312
+ - len(去除空格后的输入) > 0 → 必须视为非空
313
+ - 符号、数字、品牌名、地名等都不是"空",也不是"无效"
314
+ - 默认通过,只在明确违规时才拒绝
315
+ """
306
316
 
307
317
  # ========== Error Message Constants ==========
308
318
 
markdown_flow/core.py CHANGED
@@ -97,6 +97,7 @@ class MarkdownFlow:
97
97
  self._blocks = None
98
98
  self._model: str | None = None
99
99
  self._temperature: float | None = None
100
+ self._enable_text_validation: bool = False # Default: validation disabled for performance
100
101
 
101
102
  # Preprocess document: extract code blocks and replace with placeholders
102
103
  # This is done once during initialization, similar to Go implementation
@@ -194,6 +195,37 @@ class MarkdownFlow:
194
195
  """
195
196
  return self._temperature
196
197
 
198
+ def set_text_validation_enabled(self, enabled: bool) -> "MarkdownFlow":
199
+ """
200
+ Set whether to enable text input LLM validation.
201
+
202
+ Default is False (disabled) for performance and cost optimization.
203
+ When disabled, text inputs are accepted directly without LLM validation.
204
+ When enabled, uses ValidationTaskTemplate for LLM validation.
205
+
206
+ Affects interaction types:
207
+ - TEXT_ONLY: Pure text input
208
+ - BUTTONS_WITH_TEXT: Buttons + text fallback
209
+ - BUTTONS_MULTI_WITH_TEXT: Multi-select buttons + text fallback
210
+
211
+ Args:
212
+ enabled: True to enable validation, False to disable
213
+
214
+ Returns:
215
+ Self for method chaining
216
+ """
217
+ self._enable_text_validation = enabled
218
+ return self
219
+
220
+ def is_text_validation_enabled(self) -> bool:
221
+ """
222
+ Check if text input validation is enabled.
223
+
224
+ Returns:
225
+ True if validation is enabled, False otherwise
226
+ """
227
+ return self._enable_text_validation
228
+
197
229
  def set_prompt(self, prompt_type: str, value: str | None) -> None:
198
230
  """
199
231
  Set prompt template.
@@ -558,9 +590,30 @@ class MarkdownFlow:
558
590
  # Step 1: Match button values
559
591
  matched_values, unmatched_values = self._match_button_values(buttons, target_values)
560
592
 
561
- # Step 2: If there are unmatched values (custom text), validate with LLM
593
+ # Step 2: If there are unmatched values (custom text)
562
594
  if unmatched_values:
563
- # Create user_input for LLM validation (only custom text)
595
+ # Check if text validation is enabled
596
+ if not self._enable_text_validation:
597
+ # Validation disabled: directly accept unmatched custom text
598
+ all_values = matched_values + unmatched_values
599
+ result = LLMResult(
600
+ content="",
601
+ variables={target_variable: all_values},
602
+ metadata={
603
+ "interaction_type": str(interaction_type),
604
+ "matched_button_values": matched_values,
605
+ "custom_text_values": unmatched_values,
606
+ "validation_bypassed": True, # Mark validation was skipped
607
+ },
608
+ )
609
+ # Return generator for STREAM mode, direct result for COMPLETE mode
610
+ if mode == ProcessMode.STREAM:
611
+ def stream_generator():
612
+ yield result
613
+ return stream_generator()
614
+ return result
615
+
616
+ # Validation enabled: create user_input for LLM validation (only custom text)
564
617
  custom_input = {target_variable: unmatched_values}
565
618
 
566
619
  validation_result = self._process_llm_validation(
@@ -614,7 +667,7 @@ class MarkdownFlow:
614
667
  return stream_merge_generator()
615
668
  else:
616
669
  # All values matched buttons, return directly
617
- return LLMResult(
670
+ result = LLMResult(
618
671
  content="",
619
672
  variables={target_variable: matched_values},
620
673
  metadata={
@@ -622,6 +675,12 @@ class MarkdownFlow:
622
675
  "all_matched_buttons": True,
623
676
  },
624
677
  )
678
+ # Return generator for STREAM mode, direct result for COMPLETE mode
679
+ if mode == ProcessMode.STREAM:
680
+ def stream_generator():
681
+ yield result
682
+ return stream_generator()
683
+ return result
625
684
 
626
685
  if interaction_type in [
627
686
  InteractionType.BUTTONS_ONLY,
@@ -640,7 +699,7 @@ class MarkdownFlow:
640
699
  if interaction_type == InteractionType.NON_ASSIGNMENT_BUTTON:
641
700
  # Non-assignment buttons: ?[Continue] or ?[Continue|Cancel]
642
701
  # These buttons don't assign variables, any input completes the interaction
643
- return LLMResult(
702
+ result = LLMResult(
644
703
  content="", # Empty content indicates interaction complete
645
704
  variables={}, # Non-assignment buttons don't set variables
646
705
  metadata={
@@ -648,10 +707,34 @@ class MarkdownFlow:
648
707
  "user_input": user_input,
649
708
  },
650
709
  )
710
+ # Return generator for STREAM mode, direct result for COMPLETE mode
711
+ if mode == ProcessMode.STREAM:
712
+ def stream_generator():
713
+ yield result
714
+ return stream_generator()
715
+ return result
651
716
 
652
717
  # Text-only input type: ?[%{{sys_user_nickname}}...question]
653
- # Use LLM validation to check if input is relevant to the question
654
718
  if target_values:
719
+ # Check if text validation is enabled
720
+ if not self._enable_text_validation:
721
+ # Validation disabled: directly accept all text input
722
+ result = LLMResult(
723
+ content="",
724
+ variables={target_variable: target_values},
725
+ metadata={
726
+ "interaction_type": "text_only",
727
+ "validation_bypassed": True, # Mark validation was skipped
728
+ },
729
+ )
730
+ # Return generator for STREAM mode, direct result for COMPLETE mode
731
+ if mode == ProcessMode.STREAM:
732
+ def stream_generator():
733
+ yield result
734
+ return stream_generator()
735
+ return result
736
+
737
+ # Validation enabled: use LLM validation to check if input is relevant to the question
655
738
  return self._process_llm_validation(
656
739
  block_index=block_index,
657
740
  user_input=user_input,
@@ -728,7 +811,7 @@ class MarkdownFlow:
728
811
  if not target_values:
729
812
  if allow_text_input:
730
813
  # Allow empty input for buttons+text mode
731
- return LLMResult(
814
+ result = LLMResult(
732
815
  content="",
733
816
  variables={target_variable: []},
734
817
  metadata={
@@ -736,6 +819,12 @@ class MarkdownFlow:
736
819
  "empty_input": True,
737
820
  },
738
821
  )
822
+ # Return generator for STREAM mode, direct result for COMPLETE mode
823
+ if mode == ProcessMode.STREAM:
824
+ def stream_generator():
825
+ yield result
826
+ return stream_generator()
827
+ return result
739
828
  # Pure button mode requires input
740
829
  button_displays = [btn["display"] for btn in buttons]
741
830
  error_msg = f"Please select from: {', '.join(button_displays)}"
@@ -767,7 +856,7 @@ class MarkdownFlow:
767
856
  return self._render_error(error_msg, mode, context)
768
857
 
769
858
  # Success: return validated values
770
- return LLMResult(
859
+ result = LLMResult(
771
860
  content="",
772
861
  variables={target_variable: valid_values},
773
862
  metadata={
@@ -778,6 +867,12 @@ class MarkdownFlow:
778
867
  "total_input_count": len(target_values),
779
868
  },
780
869
  )
870
+ # Return generator for STREAM mode, direct result for COMPLETE mode
871
+ if mode == ProcessMode.STREAM:
872
+ def stream_generator():
873
+ yield result
874
+ return stream_generator()
875
+ return result
781
876
 
782
877
  def _process_llm_validation(
783
878
  self,
@@ -991,7 +1086,6 @@ class MarkdownFlow:
991
1086
  translatable["question"] = interaction_info["question"]
992
1087
 
993
1088
  # 转换为 JSON
994
- import json
995
1089
 
996
1090
  json_str = json.dumps(translatable, ensure_ascii=False)
997
1091
 
@@ -1041,7 +1135,6 @@ class MarkdownFlow:
1041
1135
  Returns:
1042
1136
  str: 重构后的交互内容
1043
1137
  """
1044
- import json
1045
1138
 
1046
1139
  # 解析原始 JSON
1047
1140
  try:
@@ -8,10 +8,6 @@ import json
8
8
  from typing import Any
9
9
 
10
10
  from ..constants import (
11
- CONTEXT_BUTTON_OPTIONS_TEMPLATE,
12
- CONTEXT_CONVERSATION_TEMPLATE,
13
- CONTEXT_QUESTION_MARKER,
14
- CONTEXT_QUESTION_TEMPLATE,
15
11
  VALIDATION_ILLEGAL_DEFAULT_REASON,
16
12
  VALIDATION_RESPONSE_ILLEGAL,
17
13
  VALIDATION_RESPONSE_OK,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.30
3
+ Version: 0.2.31
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
@@ -1,6 +1,6 @@
1
- markdown_flow/__init__.py,sha256=E-P0SvBLJKQSwj2ZijjjXsDFl9axsrX8oTfTl7YBO7w,2808
2
- markdown_flow/constants.py,sha256=aroEBhrOGY6JxofRxPFe87vesEJFY1Srm0_jRHVdtig,14274
3
- markdown_flow/core.py,sha256=bwUdblJPPWqV_Utwn3ijmjpauI_FXcZUlVG30BIa7nw,47831
1
+ markdown_flow/__init__.py,sha256=x92vtEJ3BZbfjRvIIGsV68mLxubg_QqXvqKiK9YQVE0,2808
2
+ markdown_flow/constants.py,sha256=l6I16NLgnu_7AxG8hRcSRBLedQPROvA-qqpWLjmGF8Q,13801
3
+ markdown_flow/core.py,sha256=tpUB8fHe16WCsCtq1WHzMVBsLnvBlbKsBRz1reYH8T8,51927
4
4
  markdown_flow/enums.py,sha256=Wr41zt0Ce5b3fyLtOTE2erEVp1n92g9OVaBF_BZg_l8,820
5
5
  markdown_flow/exceptions.py,sha256=9sUZ-Jd3CPLdSRqG8Pw7eMm7cv_S3VZM6jmjUU8OhIc,976
6
6
  markdown_flow/llm.py,sha256=MJRbXKj35AjLCAhWpFhS07s-m3YU2qO1HOFff05HG2I,2239
@@ -12,13 +12,13 @@ markdown_flow/parser/interaction.py,sha256=T4W7iO-iyNJnpM7SmvOH_DRlLuWSDcFyIrN2f
12
12
  markdown_flow/parser/json_parser.py,sha256=78GhyyOjlg0l4UmKKNc4zrg-4pSHzrJEt7VKqbz3uyE,1305
13
13
  markdown_flow/parser/output.py,sha256=LgxvH6-RINM50p58miQtw_fHER1JEWDGucHk5-sZ-gk,8087
14
14
  markdown_flow/parser/preprocessor.py,sha256=YO2znQo7biYAxZZIO5oyrH4h88LZPIe3SidX7ZEOS88,4877
15
- markdown_flow/parser/validation.py,sha256=fc5-zL4_vsgFQuQ0BHXlJRH5Vkx102SKJy-H72tpLK8,3647
15
+ markdown_flow/parser/validation.py,sha256=qm_YJ0NNWdFrZZfXL800OWTHGtmkX_b3PVF1te7XmTQ,3515
16
16
  markdown_flow/parser/variable.py,sha256=eJLbVOyZT8uYM5eJNv5kHLqdRoNz5iNlxHhhi2oDW94,2986
17
17
  markdown_flow/providers/__init__.py,sha256=QMr8H9gxoLr6pWXoAb11oZX_She6KWPxnRips537nQ4,319
18
18
  markdown_flow/providers/config.py,sha256=Y4Nihqj3KxI6_RyvVKF_mv4mBoPNXeLgYQgv0FqxQfU,2057
19
19
  markdown_flow/providers/openai.py,sha256=KgExRJ8QsCeU_c-Yx3IhxG2hBbYN5uZ-uf0VTMvD1LE,12326
20
- markdown_flow-0.2.30.dist-info/licenses/LICENSE,sha256=qz3BziejhHPd1xa5eVtYEU5Qp6L2pn4otuj194uGxmc,1069
21
- markdown_flow-0.2.30.dist-info/METADATA,sha256=LEehzrEIw6Q_TyvGFuAG-m0fPEKgO-QcaLgD8LcvyYM,20686
22
- markdown_flow-0.2.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- markdown_flow-0.2.30.dist-info/top_level.txt,sha256=DpigGvQuIt2L0TTLnDU5sylhiTGiZS7MmAMa2hi-AJs,14
24
- markdown_flow-0.2.30.dist-info/RECORD,,
20
+ markdown_flow-0.2.31.dist-info/licenses/LICENSE,sha256=qz3BziejhHPd1xa5eVtYEU5Qp6L2pn4otuj194uGxmc,1069
21
+ markdown_flow-0.2.31.dist-info/METADATA,sha256=isqtJHsTgU_2KPpmmPfnNnLgzBCeP4PsNTdGyUuNf8I,20686
22
+ markdown_flow-0.2.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ markdown_flow-0.2.31.dist-info/top_level.txt,sha256=DpigGvQuIt2L0TTLnDU5sylhiTGiZS7MmAMa2hi-AJs,14
24
+ markdown_flow-0.2.31.dist-info/RECORD,,