markdown-flow 0.2.80__tar.gz → 0.2.81__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.
Files changed (49) hide show
  1. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/PKG-INFO +1 -1
  2. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/__init__.py +1 -1
  3. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/core.py +17 -8
  4. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/system_prompt.md +2 -2
  5. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow.egg-info/PKG-INFO +1 -1
  6. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow.egg-info/SOURCES.txt +1 -0
  7. markdown_flow-0.2.81/tests/test_error_render_context.py +58 -0
  8. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/LICENSE +0 -0
  9. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/README.md +0 -0
  10. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/constants.py +0 -0
  11. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/constants_system_prompt.py +0 -0
  12. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/enums.py +0 -0
  13. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/exceptions.py +0 -0
  14. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/formatter/__init__.py +0 -0
  15. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/formatter/classifier.py +0 -0
  16. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/formatter/format.py +0 -0
  17. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/formatter/patterns.py +0 -0
  18. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/formatter/stream.py +0 -0
  19. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/formatter/types.py +0 -0
  20. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/llm.py +0 -0
  21. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/models.py +0 -0
  22. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/__init__.py +0 -0
  23. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/code_fence_utils.py +0 -0
  24. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/html_comment_utils.py +0 -0
  25. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/interaction.py +0 -0
  26. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/json_parser.py +0 -0
  27. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/output.py +0 -0
  28. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/preprocessor.py +0 -0
  29. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/validation.py +0 -0
  30. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/parser/variable.py +0 -0
  31. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/providers/__init__.py +0 -0
  32. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/providers/config.py +0 -0
  33. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/providers/openai.py +0 -0
  34. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/tag_filter.py +0 -0
  35. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow/utils.py +0 -0
  36. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow.egg-info/dependency_links.txt +0 -0
  37. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/markdown_flow.egg-info/top_level.txt +0 -0
  38. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/pyproject.toml +0 -0
  39. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/setup.cfg +0 -0
  40. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_dynamic_interaction.py +0 -0
  41. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_formatter.py +0 -0
  42. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_formatter_stream.py +0 -0
  43. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_html_comment_utils.py +0 -0
  44. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_markdownflow_basic.py +0 -0
  45. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_parser_interaction.py +0 -0
  46. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_parser_output.py +0 -0
  47. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_parser_variable.py +0 -0
  48. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_preprocessor.py +0 -0
  49. {markdown_flow-0.2.80 → markdown_flow-0.2.81}/tests/test_preserved_simple.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.80
3
+ Version: 0.2.81
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
@@ -88,4 +88,4 @@ __all__ = [
88
88
  "replace_variables_in_text",
89
89
  ]
90
90
 
91
- __version__ = "0.2.80"
91
+ __version__ = "0.2.81"
@@ -803,7 +803,7 @@ class MarkdownFlow:
803
803
  # Basic validation
804
804
  if not user_input or not any(values for values in user_input.values()):
805
805
  error_msg = INPUT_EMPTY_ERROR
806
- return self._render_error(error_msg, mode, context)
806
+ return self._render_error(error_msg, mode, context, variables)
807
807
 
808
808
  # Get the target variable value from user_input
809
809
  target_values = user_input.get(target_variable, [])
@@ -817,7 +817,7 @@ class MarkdownFlow:
817
817
 
818
818
  if "error" in parse_result:
819
819
  error_msg = INTERACTION_PARSE_ERROR.format(error=parse_result["error"])
820
- return self._render_error(error_msg, mode, context)
820
+ return self._render_error(error_msg, mode, context, variables)
821
821
 
822
822
  interaction_type = parse_result.get("type")
823
823
 
@@ -942,6 +942,7 @@ class MarkdownFlow:
942
942
  mode,
943
943
  interaction_type,
944
944
  context,
945
+ variables,
945
946
  )
946
947
 
947
948
  if interaction_type == InteractionType.NON_ASSIGNMENT_BUTTON:
@@ -995,7 +996,7 @@ class MarkdownFlow:
995
996
  context=context,
996
997
  )
997
998
  error_msg = f"No input provided for variable '{target_variable}'"
998
- return self._render_error(error_msg, mode, context)
999
+ return self._render_error(error_msg, mode, context, variables)
999
1000
 
1000
1001
  def _match_button_values(
1001
1002
  self,
@@ -1038,6 +1039,7 @@ class MarkdownFlow:
1038
1039
  mode: ProcessMode,
1039
1040
  interaction_type: InteractionType,
1040
1041
  context: list[dict[str, str]] | None = None,
1042
+ variables: dict[str, str | list[str]] | None = None,
1041
1043
  ) -> LLMResult | Generator[LLMResult, None, None]:
1042
1044
  """
1043
1045
  Simplified button validation with new input format.
@@ -1082,7 +1084,7 @@ class MarkdownFlow:
1082
1084
  # Pure button mode requires input
1083
1085
  button_displays = [btn["display"] for btn in buttons]
1084
1086
  error_msg = f"Please select from: {', '.join(button_displays)}"
1085
- return self._render_error(error_msg, mode, context)
1087
+ return self._render_error(error_msg, mode, context, variables)
1086
1088
 
1087
1089
  # Validate input values against available buttons
1088
1090
  valid_values = []
@@ -1107,7 +1109,7 @@ class MarkdownFlow:
1107
1109
  if invalid_values and not allow_text_input:
1108
1110
  button_displays = [btn["display"] for btn in buttons]
1109
1111
  error_msg = f"Invalid options: {', '.join(invalid_values)}. Please select from: {', '.join(button_displays)}"
1110
- return self._render_error(error_msg, mode, context)
1112
+ return self._render_error(error_msg, mode, context, variables)
1111
1113
 
1112
1114
  # Success: return validated values
1113
1115
  result = LLMResult(
@@ -1180,13 +1182,14 @@ class MarkdownFlow:
1180
1182
  error_message: str,
1181
1183
  mode: ProcessMode,
1182
1184
  context: list[dict[str, str]] | None = None,
1185
+ variables: dict[str, str | list[str]] | None = None,
1183
1186
  ) -> LLMResult | Generator[LLMResult, None, None]:
1184
1187
  """Render user-friendly error message."""
1185
1188
  # Truncate context to configured maximum length
1186
1189
  truncated_context = self._truncate_context(context)
1187
1190
 
1188
1191
  # Build error messages with context
1189
- messages = self._build_error_render_messages(error_message, truncated_context)
1192
+ messages = self._build_error_render_messages(error_message, truncated_context, variables)
1190
1193
 
1191
1194
  if mode == ProcessMode.COMPLETE:
1192
1195
  if not self._llm_provider:
@@ -1531,6 +1534,7 @@ class MarkdownFlow:
1531
1534
  self,
1532
1535
  error_message: str,
1533
1536
  context: list[dict[str, str]] | None = None,
1537
+ variables: dict[str, str | list[str]] | None = None,
1534
1538
  ) -> list[dict[str, str]]:
1535
1539
  """Build error rendering messages."""
1536
1540
  render_prompt = f"""{self._interaction_error_prompt}
@@ -1545,10 +1549,15 @@ Original Error: {error_message}
1545
1549
 
1546
1550
  messages.append({"role": "system", "content": render_prompt})
1547
1551
 
1548
- # Add conversation history context if provided
1552
+ # Add conversation history context if provided.
1553
+ # Transform interaction syntax in the context the same way the content
1554
+ # path does, so raw ?[...] blocks are expanded to {user}/{assistant}
1555
+ # pairs instead of leaking into this auxiliary LLM call.
1549
1556
  truncated_context = self._truncate_context(context)
1550
1557
  if truncated_context:
1551
- messages.extend(truncated_context)
1558
+ transformed_context = self._transform_context_messages(truncated_context, variables)
1559
+ if transformed_context:
1560
+ messages.extend(transformed_context)
1552
1561
 
1553
1562
  messages.append({"role": "user", "content": error_message})
1554
1563
 
@@ -26,7 +26,7 @@
26
26
 
27
27
  每屏 = 一个铺满视口的固定容器,不可滚动。外层容器写法:
28
28
 
29
- ```
29
+ ```text
30
30
  <div style="width:100%; min-height:100vh; overflow-x:hidden; overflow-y:auto; display:flex; flex-direction:column; align-items:center; padding:1em; font-size:clamp(12px,calc(100vw/48),3vh)">
31
31
  <!-- 内容 -->
32
32
  </div>
@@ -34,7 +34,7 @@
34
34
 
35
35
  每屏 HTML 后必须紧跟:
36
36
 
37
- ```
37
+ ```text
38
38
  <style>
39
39
  *,*::before,*::after{box-sizing:border-box;overflow-wrap:break-word;word-wrap:break-word}
40
40
  </style>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.80
3
+ Version: 0.2.81
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
@@ -35,6 +35,7 @@ markdown_flow/providers/__init__.py
35
35
  markdown_flow/providers/config.py
36
36
  markdown_flow/providers/openai.py
37
37
  tests/test_dynamic_interaction.py
38
+ tests/test_error_render_context.py
38
39
  tests/test_formatter.py
39
40
  tests/test_formatter_stream.py
40
41
  tests/test_html_comment_utils.py
@@ -0,0 +1,58 @@
1
+ """
2
+ Unit tests for context transformation in the error-render path.
3
+
4
+ The error-render path is an auxiliary LLM call triggered when interaction
5
+ input validation fails. It must transform interaction syntax in the provided
6
+ context the same way the content path does, so raw ?[...] blocks are expanded
7
+ into {user}/{assistant} pairs instead of leaking into the prompt.
8
+ """
9
+
10
+ from markdown_flow import MarkdownFlow
11
+
12
+
13
+ def _build_messages(context, variables):
14
+ mf = MarkdownFlow("Doc", llm_provider=None)
15
+ truncated = mf._truncate_context(context)
16
+ return mf._build_error_render_messages("some error", truncated, variables)
17
+
18
+
19
+ def test_error_render_expands_interaction_with_variable():
20
+ """An interaction with a resolvable variable becomes user(value)+assistant(ok)."""
21
+ context = [
22
+ {"role": "user", "content": "What is your name?"},
23
+ {"role": "assistant", "content": "?[%{{nickname}} ...What is your nickname?]"},
24
+ ]
25
+ variables = {"nickname": "Alice"}
26
+
27
+ messages = _build_messages(context, variables)
28
+
29
+ # Raw interaction syntax must not leak into the prompt.
30
+ assert all("?[" not in m["content"] for m in messages)
31
+ # The interaction is expanded to a clean user/assistant pair.
32
+ assert {"role": "user", "content": "Alice"} in messages
33
+ expanded = [m for m in messages if m["role"] == "assistant" and m["content"] == "ok"]
34
+ assert expanded, "expected an assistant 'ok' acknowledgement"
35
+
36
+
37
+ def test_error_render_skips_interaction_without_value():
38
+ """A variable interaction with no resolvable value is dropped, not leaked."""
39
+ context = [
40
+ {"role": "assistant", "content": "?[%{{nickname}} ...What is your nickname?]"},
41
+ ]
42
+
43
+ messages = _build_messages(context, variables={})
44
+
45
+ assert all("?[" not in m["content"] for m in messages)
46
+ assert all(m["content"] != "Alice" for m in messages)
47
+
48
+
49
+ def test_error_render_no_variable_interaction_becomes_ok():
50
+ """A button-only interaction (no variable) becomes ok/ok."""
51
+ context = [
52
+ {"role": "assistant", "content": "?[Continue|Cancel]"},
53
+ ]
54
+
55
+ messages = _build_messages(context, variables=None)
56
+
57
+ assert all("?[" not in m["content"] for m in messages)
58
+ assert {"role": "user", "content": "ok"} in messages
File without changes
File without changes
File without changes