markdown-flow 0.2.79__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.
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/PKG-INFO +1 -1
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/__init__.py +1 -2
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/core.py +45 -34
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/system_prompt.md +3 -3
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow.egg-info/PKG-INFO +1 -1
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow.egg-info/SOURCES.txt +1 -0
- markdown_flow-0.2.81/tests/test_error_render_context.py +58 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/LICENSE +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/README.md +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/constants.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/constants_system_prompt.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/enums.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/exceptions.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/formatter/__init__.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/formatter/classifier.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/formatter/format.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/formatter/patterns.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/formatter/stream.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/formatter/types.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/llm.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/models.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/__init__.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/code_fence_utils.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/html_comment_utils.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/interaction.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/json_parser.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/output.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/preprocessor.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/validation.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/parser/variable.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/providers/__init__.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/providers/config.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/providers/openai.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/tag_filter.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow/utils.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow.egg-info/dependency_links.txt +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/markdown_flow.egg-info/top_level.txt +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/pyproject.toml +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/setup.cfg +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_dynamic_interaction.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_formatter.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_formatter_stream.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_html_comment_utils.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_markdownflow_basic.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_parser_interaction.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_parser_output.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_parser_variable.py +0 -0
- {markdown_flow-0.2.79 → markdown_flow-0.2.81}/tests/test_preprocessor.py +0 -0
- {markdown_flow-0.2.79 → 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.
|
|
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
|
|
@@ -666,43 +666,45 @@ class MarkdownFlow:
|
|
|
666
666
|
|
|
667
667
|
# Extract translatable content (JSON format)
|
|
668
668
|
translatable_json, interaction_info = self._extract_translatable_content(processed_block.content)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
669
|
+
|
|
670
|
+
def _render_as_is(*, translation_skipped: bool = False):
|
|
671
|
+
"""Return the interaction content unchanged, honoring the mode.
|
|
672
|
+
|
|
673
|
+
STREAM mode must return a generator yielding LLMResult (callers
|
|
674
|
+
iterate the result); COMPLETE returns the LLMResult directly.
|
|
675
|
+
"""
|
|
676
|
+
metadata = {
|
|
677
|
+
"block_type": "interaction",
|
|
678
|
+
"block_index": block_index,
|
|
679
|
+
}
|
|
680
|
+
if translation_skipped:
|
|
681
|
+
metadata["translation_skipped"] = True
|
|
682
|
+
result = LLMResult(
|
|
672
683
|
content=processed_block.content,
|
|
673
684
|
type=ElementType.INTERACTION,
|
|
674
685
|
number=block_index,
|
|
675
|
-
metadata=
|
|
676
|
-
"block_type": "interaction",
|
|
677
|
-
"block_index": block_index,
|
|
678
|
-
},
|
|
686
|
+
metadata=metadata,
|
|
679
687
|
)
|
|
688
|
+
if mode == ProcessMode.STREAM:
|
|
689
|
+
|
|
690
|
+
def stream_generator():
|
|
691
|
+
yield result
|
|
692
|
+
|
|
693
|
+
return stream_generator()
|
|
694
|
+
return result
|
|
695
|
+
|
|
696
|
+
if not interaction_info:
|
|
697
|
+
# Parse failed, return original content
|
|
698
|
+
return _render_as_is()
|
|
680
699
|
|
|
681
700
|
# If no translatable content, return directly
|
|
682
701
|
if not translatable_json or translatable_json == "{}":
|
|
683
|
-
return
|
|
684
|
-
content=processed_block.content,
|
|
685
|
-
type=ElementType.INTERACTION,
|
|
686
|
-
number=block_index,
|
|
687
|
-
metadata={
|
|
688
|
-
"block_type": "interaction",
|
|
689
|
-
"block_index": block_index,
|
|
690
|
-
},
|
|
691
|
-
)
|
|
702
|
+
return _render_as_is()
|
|
692
703
|
|
|
693
704
|
# If no output language is configured, skip translation entirely:
|
|
694
705
|
# return the interaction content as-is without any LLM call.
|
|
695
706
|
if not self._output_language:
|
|
696
|
-
return
|
|
697
|
-
content=processed_block.content,
|
|
698
|
-
type=ElementType.INTERACTION,
|
|
699
|
-
number=block_index,
|
|
700
|
-
metadata={
|
|
701
|
-
"block_type": "interaction",
|
|
702
|
-
"block_index": block_index,
|
|
703
|
-
"translation_skipped": True,
|
|
704
|
-
},
|
|
705
|
-
)
|
|
707
|
+
return _render_as_is(translation_skipped=True)
|
|
706
708
|
|
|
707
709
|
# Build translation messages
|
|
708
710
|
messages = self._build_translation_messages(translatable_json)
|
|
@@ -801,7 +803,7 @@ class MarkdownFlow:
|
|
|
801
803
|
# Basic validation
|
|
802
804
|
if not user_input or not any(values for values in user_input.values()):
|
|
803
805
|
error_msg = INPUT_EMPTY_ERROR
|
|
804
|
-
return self._render_error(error_msg, mode, context)
|
|
806
|
+
return self._render_error(error_msg, mode, context, variables)
|
|
805
807
|
|
|
806
808
|
# Get the target variable value from user_input
|
|
807
809
|
target_values = user_input.get(target_variable, [])
|
|
@@ -815,7 +817,7 @@ class MarkdownFlow:
|
|
|
815
817
|
|
|
816
818
|
if "error" in parse_result:
|
|
817
819
|
error_msg = INTERACTION_PARSE_ERROR.format(error=parse_result["error"])
|
|
818
|
-
return self._render_error(error_msg, mode, context)
|
|
820
|
+
return self._render_error(error_msg, mode, context, variables)
|
|
819
821
|
|
|
820
822
|
interaction_type = parse_result.get("type")
|
|
821
823
|
|
|
@@ -940,6 +942,7 @@ class MarkdownFlow:
|
|
|
940
942
|
mode,
|
|
941
943
|
interaction_type,
|
|
942
944
|
context,
|
|
945
|
+
variables,
|
|
943
946
|
)
|
|
944
947
|
|
|
945
948
|
if interaction_type == InteractionType.NON_ASSIGNMENT_BUTTON:
|
|
@@ -993,7 +996,7 @@ class MarkdownFlow:
|
|
|
993
996
|
context=context,
|
|
994
997
|
)
|
|
995
998
|
error_msg = f"No input provided for variable '{target_variable}'"
|
|
996
|
-
return self._render_error(error_msg, mode, context)
|
|
999
|
+
return self._render_error(error_msg, mode, context, variables)
|
|
997
1000
|
|
|
998
1001
|
def _match_button_values(
|
|
999
1002
|
self,
|
|
@@ -1036,6 +1039,7 @@ class MarkdownFlow:
|
|
|
1036
1039
|
mode: ProcessMode,
|
|
1037
1040
|
interaction_type: InteractionType,
|
|
1038
1041
|
context: list[dict[str, str]] | None = None,
|
|
1042
|
+
variables: dict[str, str | list[str]] | None = None,
|
|
1039
1043
|
) -> LLMResult | Generator[LLMResult, None, None]:
|
|
1040
1044
|
"""
|
|
1041
1045
|
Simplified button validation with new input format.
|
|
@@ -1080,7 +1084,7 @@ class MarkdownFlow:
|
|
|
1080
1084
|
# Pure button mode requires input
|
|
1081
1085
|
button_displays = [btn["display"] for btn in buttons]
|
|
1082
1086
|
error_msg = f"Please select from: {', '.join(button_displays)}"
|
|
1083
|
-
return self._render_error(error_msg, mode, context)
|
|
1087
|
+
return self._render_error(error_msg, mode, context, variables)
|
|
1084
1088
|
|
|
1085
1089
|
# Validate input values against available buttons
|
|
1086
1090
|
valid_values = []
|
|
@@ -1105,7 +1109,7 @@ class MarkdownFlow:
|
|
|
1105
1109
|
if invalid_values and not allow_text_input:
|
|
1106
1110
|
button_displays = [btn["display"] for btn in buttons]
|
|
1107
1111
|
error_msg = f"Invalid options: {', '.join(invalid_values)}. Please select from: {', '.join(button_displays)}"
|
|
1108
|
-
return self._render_error(error_msg, mode, context)
|
|
1112
|
+
return self._render_error(error_msg, mode, context, variables)
|
|
1109
1113
|
|
|
1110
1114
|
# Success: return validated values
|
|
1111
1115
|
result = LLMResult(
|
|
@@ -1178,13 +1182,14 @@ class MarkdownFlow:
|
|
|
1178
1182
|
error_message: str,
|
|
1179
1183
|
mode: ProcessMode,
|
|
1180
1184
|
context: list[dict[str, str]] | None = None,
|
|
1185
|
+
variables: dict[str, str | list[str]] | None = None,
|
|
1181
1186
|
) -> LLMResult | Generator[LLMResult, None, None]:
|
|
1182
1187
|
"""Render user-friendly error message."""
|
|
1183
1188
|
# Truncate context to configured maximum length
|
|
1184
1189
|
truncated_context = self._truncate_context(context)
|
|
1185
1190
|
|
|
1186
1191
|
# Build error messages with context
|
|
1187
|
-
messages = self._build_error_render_messages(error_message, truncated_context)
|
|
1192
|
+
messages = self._build_error_render_messages(error_message, truncated_context, variables)
|
|
1188
1193
|
|
|
1189
1194
|
if mode == ProcessMode.COMPLETE:
|
|
1190
1195
|
if not self._llm_provider:
|
|
@@ -1529,6 +1534,7 @@ class MarkdownFlow:
|
|
|
1529
1534
|
self,
|
|
1530
1535
|
error_message: str,
|
|
1531
1536
|
context: list[dict[str, str]] | None = None,
|
|
1537
|
+
variables: dict[str, str | list[str]] | None = None,
|
|
1532
1538
|
) -> list[dict[str, str]]:
|
|
1533
1539
|
"""Build error rendering messages."""
|
|
1534
1540
|
render_prompt = f"""{self._interaction_error_prompt}
|
|
@@ -1543,10 +1549,15 @@ Original Error: {error_message}
|
|
|
1543
1549
|
|
|
1544
1550
|
messages.append({"role": "system", "content": render_prompt})
|
|
1545
1551
|
|
|
1546
|
-
# 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.
|
|
1547
1556
|
truncated_context = self._truncate_context(context)
|
|
1548
1557
|
if truncated_context:
|
|
1549
|
-
|
|
1558
|
+
transformed_context = self._transform_context_messages(truncated_context, variables)
|
|
1559
|
+
if transformed_context:
|
|
1560
|
+
messages.extend(transformed_context)
|
|
1550
1561
|
|
|
1551
1562
|
messages.append({"role": "user", "content": error_message})
|
|
1552
1563
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
# 二、html展示内容生成规则
|
|
11
11
|
|
|
12
|
-
仅当用户要求生成视觉内容(PPT/页面/HTML
|
|
12
|
+
仅当用户要求生成视觉内容(PPT/页面/HTML/图表/图片)时启用。如果用户要求只生成内容,则不启用该规则。
|
|
13
13
|
|
|
14
14
|
## 1. 渲染机制
|
|
15
15
|
|
|
@@ -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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|