markdown-flow 0.2.80__tar.gz → 0.2.82__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 (48) hide show
  1. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/PKG-INFO +1 -1
  2. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/__init__.py +1 -1
  3. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/constants.py +13 -0
  4. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/core.py +104 -10
  5. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/system_prompt.md +2 -2
  6. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow.egg-info/PKG-INFO +1 -1
  7. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/LICENSE +0 -0
  8. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/README.md +0 -0
  9. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/constants_system_prompt.py +0 -0
  10. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/enums.py +0 -0
  11. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/exceptions.py +0 -0
  12. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/formatter/__init__.py +0 -0
  13. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/formatter/classifier.py +0 -0
  14. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/formatter/format.py +0 -0
  15. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/formatter/patterns.py +0 -0
  16. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/formatter/stream.py +0 -0
  17. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/formatter/types.py +0 -0
  18. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/llm.py +0 -0
  19. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/models.py +0 -0
  20. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/__init__.py +0 -0
  21. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/code_fence_utils.py +0 -0
  22. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/html_comment_utils.py +0 -0
  23. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/interaction.py +0 -0
  24. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/json_parser.py +0 -0
  25. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/output.py +0 -0
  26. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/preprocessor.py +0 -0
  27. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/validation.py +0 -0
  28. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/parser/variable.py +0 -0
  29. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/providers/__init__.py +0 -0
  30. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/providers/config.py +0 -0
  31. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/providers/openai.py +0 -0
  32. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/tag_filter.py +0 -0
  33. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow/utils.py +0 -0
  34. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow.egg-info/SOURCES.txt +0 -0
  35. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow.egg-info/dependency_links.txt +0 -0
  36. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/markdown_flow.egg-info/top_level.txt +0 -0
  37. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/pyproject.toml +0 -0
  38. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/setup.cfg +0 -0
  39. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_dynamic_interaction.py +0 -0
  40. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_formatter.py +0 -0
  41. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_formatter_stream.py +0 -0
  42. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_html_comment_utils.py +0 -0
  43. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_markdownflow_basic.py +0 -0
  44. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_parser_interaction.py +0 -0
  45. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_parser_output.py +0 -0
  46. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_parser_variable.py +0 -0
  47. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/tests/test_preprocessor.py +0 -0
  48. {markdown_flow-0.2.80 → markdown_flow-0.2.82}/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.82
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.82"
@@ -237,3 +237,16 @@ CONTEXT_CONVERSATION_TEMPLATE = f"{CONTEXT_CONVERSATION_MARKER}\n{{content}}"
237
237
  CONTEXT_BUTTON_OPTIONS_TEMPLATE = (
238
238
  f"{CONTEXT_BUTTON_OPTIONS_MARKER}\n可选的预定义选项包括:{{button_options}}\n注意:用户如果选择了这些选项,都应该接受;如果输入了自定义内容,只要是对问题的合理回答即可接受。"
239
239
  )
240
+
241
+ # Next interaction context prompt templates
242
+ NEXT_INTERACTION_CONTEXT_INTRO = (
243
+ "The next interaction will appear immediately after this content. When generating the current content, connect to it naturally. "
244
+ "You may briefly restate, explain, or set up the available choices so the user understands what they will decide next, but do not output the interaction syntax or answer on the user's behalf."
245
+ )
246
+ NEXT_INTERACTION_TEXT_INPUT_TEMPLATE = "The next interaction asks the user to answer in text: {question}"
247
+ NEXT_INTERACTION_SINGLE_CHOICE_TEMPLATE = "The next interaction is a single-choice question. The user will choose one option from: {options}"
248
+ NEXT_INTERACTION_MULTIPLE_CHOICE_TEMPLATE = "The next interaction is a multiple-choice question. The user can choose one or more options from: {options}"
249
+ NEXT_INTERACTION_SINGLE_CHOICE_WITH_TEXT_INTRO = "The next interaction is a single-choice question with an optional custom text answer."
250
+ NEXT_INTERACTION_MULTIPLE_CHOICE_WITH_TEXT_INTRO = "The next interaction is a multiple-choice question with an optional custom text answer."
251
+ NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE = "The predefined options are: {options}."
252
+ NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE = "The custom text prompt is: {question}"
@@ -25,6 +25,14 @@ from .constants import (
25
25
  INTERACTION_PROMPT_BASE,
26
26
  INTERACTION_PROMPT_WITH_TRANSLATION,
27
27
  LLM_PROVIDER_REQUIRED_ERROR,
28
+ NEXT_INTERACTION_CONTEXT_INTRO,
29
+ NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE,
30
+ NEXT_INTERACTION_MULTIPLE_CHOICE_TEMPLATE,
31
+ NEXT_INTERACTION_MULTIPLE_CHOICE_WITH_TEXT_INTRO,
32
+ NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE,
33
+ NEXT_INTERACTION_SINGLE_CHOICE_TEMPLATE,
34
+ NEXT_INTERACTION_SINGLE_CHOICE_WITH_TEXT_INTRO,
35
+ NEXT_INTERACTION_TEXT_INPUT_TEMPLATE,
28
36
  OUTPUT_LANGUAGE_INSTRUCTION_BOTTOM,
29
37
  OUTPUT_LANGUAGE_INSTRUCTION_TOP,
30
38
  UNSUPPORTED_PROMPT_TYPE_ERROR,
@@ -803,7 +811,7 @@ class MarkdownFlow:
803
811
  # Basic validation
804
812
  if not user_input or not any(values for values in user_input.values()):
805
813
  error_msg = INPUT_EMPTY_ERROR
806
- return self._render_error(error_msg, mode, context)
814
+ return self._render_error(error_msg, mode, context, variables)
807
815
 
808
816
  # Get the target variable value from user_input
809
817
  target_values = user_input.get(target_variable, [])
@@ -817,7 +825,7 @@ class MarkdownFlow:
817
825
 
818
826
  if "error" in parse_result:
819
827
  error_msg = INTERACTION_PARSE_ERROR.format(error=parse_result["error"])
820
- return self._render_error(error_msg, mode, context)
828
+ return self._render_error(error_msg, mode, context, variables)
821
829
 
822
830
  interaction_type = parse_result.get("type")
823
831
 
@@ -942,6 +950,7 @@ class MarkdownFlow:
942
950
  mode,
943
951
  interaction_type,
944
952
  context,
953
+ variables,
945
954
  )
946
955
 
947
956
  if interaction_type == InteractionType.NON_ASSIGNMENT_BUTTON:
@@ -995,7 +1004,7 @@ class MarkdownFlow:
995
1004
  context=context,
996
1005
  )
997
1006
  error_msg = f"No input provided for variable '{target_variable}'"
998
- return self._render_error(error_msg, mode, context)
1007
+ return self._render_error(error_msg, mode, context, variables)
999
1008
 
1000
1009
  def _match_button_values(
1001
1010
  self,
@@ -1038,6 +1047,7 @@ class MarkdownFlow:
1038
1047
  mode: ProcessMode,
1039
1048
  interaction_type: InteractionType,
1040
1049
  context: list[dict[str, str]] | None = None,
1050
+ variables: dict[str, str | list[str]] | None = None,
1041
1051
  ) -> LLMResult | Generator[LLMResult, None, None]:
1042
1052
  """
1043
1053
  Simplified button validation with new input format.
@@ -1082,7 +1092,7 @@ class MarkdownFlow:
1082
1092
  # Pure button mode requires input
1083
1093
  button_displays = [btn["display"] for btn in buttons]
1084
1094
  error_msg = f"Please select from: {', '.join(button_displays)}"
1085
- return self._render_error(error_msg, mode, context)
1095
+ return self._render_error(error_msg, mode, context, variables)
1086
1096
 
1087
1097
  # Validate input values against available buttons
1088
1098
  valid_values = []
@@ -1107,7 +1117,7 @@ class MarkdownFlow:
1107
1117
  if invalid_values and not allow_text_input:
1108
1118
  button_displays = [btn["display"] for btn in buttons]
1109
1119
  error_msg = f"Invalid options: {', '.join(invalid_values)}. Please select from: {', '.join(button_displays)}"
1110
- return self._render_error(error_msg, mode, context)
1120
+ return self._render_error(error_msg, mode, context, variables)
1111
1121
 
1112
1122
  # Success: return validated values
1113
1123
  result = LLMResult(
@@ -1180,13 +1190,14 @@ class MarkdownFlow:
1180
1190
  error_message: str,
1181
1191
  mode: ProcessMode,
1182
1192
  context: list[dict[str, str]] | None = None,
1193
+ variables: dict[str, str | list[str]] | None = None,
1183
1194
  ) -> LLMResult | Generator[LLMResult, None, None]:
1184
1195
  """Render user-friendly error message."""
1185
1196
  # Truncate context to configured maximum length
1186
1197
  truncated_context = self._truncate_context(context)
1187
1198
 
1188
1199
  # Build error messages with context
1189
- messages = self._build_error_render_messages(error_message, truncated_context)
1200
+ messages = self._build_error_render_messages(error_message, truncated_context, variables)
1190
1201
 
1191
1202
  if mode == ProcessMode.COMPLETE:
1192
1203
  if not self._llm_provider:
@@ -1283,11 +1294,88 @@ class MarkdownFlow:
1283
1294
  if self._output_language:
1284
1295
  user_content = f"<output_language_instruction>\n🚨 OUTPUT: 100% {self._output_language} - Translate ALL non-{self._output_language} words/phrases to {self._output_language} 🚨\n</output_language_instruction>\n\n{user_content}"
1285
1296
 
1297
+ next_interaction_context = self._build_next_interaction_context_prompt(block_index, variables)
1298
+ if next_interaction_context:
1299
+ user_content = f"{user_content}\n\n{next_interaction_context}"
1300
+
1286
1301
  # Add processed content as user message (as instruction to LLM)
1287
1302
  messages.append({"role": "user", "content": user_content})
1288
1303
 
1289
1304
  return messages
1290
1305
 
1306
+ def _build_next_interaction_context_prompt(
1307
+ self,
1308
+ block_index: int,
1309
+ variables: dict[str, str | list[str]] | None,
1310
+ ) -> str:
1311
+ """Build natural prompt context from the immediately following interaction block."""
1312
+ blocks = self.get_all_blocks()
1313
+ next_index = block_index + 1
1314
+ if next_index >= len(blocks):
1315
+ return ""
1316
+
1317
+ next_block = blocks[next_index]
1318
+ if next_block.block_type != BlockType.INTERACTION:
1319
+ return ""
1320
+
1321
+ interaction_content = replace_variables_in_text(next_block.content, variables or {})
1322
+ parse_result = InteractionParser().parse(interaction_content)
1323
+ if parse_result.get("error"):
1324
+ return ""
1325
+
1326
+ question = parse_result.get("question", "").strip()
1327
+ buttons = parse_result.get("buttons") or []
1328
+ option_displays = [button.get("display", "").strip() for button in buttons if button.get("display", "").strip()]
1329
+
1330
+ detail = self._format_next_interaction_detail(parse_result.get("type"), question, option_displays)
1331
+ if not detail:
1332
+ return ""
1333
+
1334
+ return f"{NEXT_INTERACTION_CONTEXT_INTRO}\n{detail}"
1335
+
1336
+ def _format_next_interaction_detail(
1337
+ self,
1338
+ interaction_type: InteractionType | None,
1339
+ question: str,
1340
+ option_displays: list[str],
1341
+ ) -> str:
1342
+ """Format the type-specific part of the next interaction prompt."""
1343
+ options = json.dumps(option_displays, ensure_ascii=False) if option_displays else ""
1344
+ question_text = json.dumps(question, ensure_ascii=False) if question else ""
1345
+
1346
+ if interaction_type == InteractionType.TEXT_ONLY:
1347
+ if not question:
1348
+ return ""
1349
+ return NEXT_INTERACTION_TEXT_INPUT_TEMPLATE.format(question=question_text)
1350
+
1351
+ if interaction_type in [InteractionType.BUTTONS_ONLY, InteractionType.NON_ASSIGNMENT_BUTTON]:
1352
+ if not options:
1353
+ return ""
1354
+ return NEXT_INTERACTION_SINGLE_CHOICE_TEMPLATE.format(options=options)
1355
+
1356
+ if interaction_type == InteractionType.BUTTONS_MULTI_SELECT:
1357
+ if not options:
1358
+ return ""
1359
+ return NEXT_INTERACTION_MULTIPLE_CHOICE_TEMPLATE.format(options=options)
1360
+
1361
+ if interaction_type == InteractionType.BUTTONS_WITH_TEXT:
1362
+ detail_parts = [NEXT_INTERACTION_SINGLE_CHOICE_WITH_TEXT_INTRO]
1363
+ if options:
1364
+ detail_parts.append(NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE.format(options=options))
1365
+ if question:
1366
+ detail_parts.append(NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE.format(question=question_text))
1367
+ return " ".join(detail_parts)
1368
+
1369
+ if interaction_type == InteractionType.BUTTONS_MULTI_WITH_TEXT:
1370
+ detail_parts = [NEXT_INTERACTION_MULTIPLE_CHOICE_WITH_TEXT_INTRO]
1371
+ if options:
1372
+ detail_parts.append(NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE.format(options=options))
1373
+ if question:
1374
+ detail_parts.append(NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE.format(question=question_text))
1375
+ return " ".join(detail_parts)
1376
+
1377
+ return ""
1378
+
1291
1379
  def _extract_translatable_content(self, interaction_content: str) -> tuple[str, dict[str, Any] | None]:
1292
1380
  """Extract translatable parts from interaction content as JSON format
1293
1381
 
@@ -1531,6 +1619,7 @@ class MarkdownFlow:
1531
1619
  self,
1532
1620
  error_message: str,
1533
1621
  context: list[dict[str, str]] | None = None,
1622
+ variables: dict[str, str | list[str]] | None = None,
1534
1623
  ) -> list[dict[str, str]]:
1535
1624
  """Build error rendering messages."""
1536
1625
  render_prompt = f"""{self._interaction_error_prompt}
@@ -1545,10 +1634,15 @@ Original Error: {error_message}
1545
1634
 
1546
1635
  messages.append({"role": "system", "content": render_prompt})
1547
1636
 
1548
- # Add conversation history context if provided
1549
- truncated_context = self._truncate_context(context)
1550
- if truncated_context:
1551
- messages.extend(truncated_context)
1637
+ # Add conversation history context if provided.
1638
+ # Context is already truncated by the caller (_render_error), so reuse it
1639
+ # directly here. Transform interaction syntax in the context the same way
1640
+ # the content path does, so raw ?[...] blocks are expanded to
1641
+ # {user}/{assistant} pairs instead of leaking into this auxiliary LLM call.
1642
+ if context:
1643
+ transformed_context = self._transform_context_messages(context, variables)
1644
+ if transformed_context:
1645
+ messages.extend(transformed_context)
1552
1646
 
1553
1647
  messages.append({"role": "user", "content": error_message})
1554
1648
 
@@ -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.82
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
File without changes
File without changes
File without changes