markdown-flow 0.2.81__tar.gz → 0.2.83__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.81 → markdown_flow-0.2.83}/PKG-INFO +2 -2
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/README.md +1 -1
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/__init__.py +1 -1
- markdown_flow-0.2.83/markdown_flow/constants.py +254 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/core.py +91 -6
- markdown_flow-0.2.83/markdown_flow/system_prompt.md +140 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/PKG-INFO +2 -2
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/SOURCES.txt +1 -12
- markdown_flow-0.2.81/markdown_flow/constants.py +0 -239
- markdown_flow-0.2.81/markdown_flow/system_prompt.md +0 -140
- markdown_flow-0.2.81/tests/test_dynamic_interaction.py +0 -936
- markdown_flow-0.2.81/tests/test_error_render_context.py +0 -58
- markdown_flow-0.2.81/tests/test_formatter.py +0 -372
- markdown_flow-0.2.81/tests/test_formatter_stream.py +0 -277
- markdown_flow-0.2.81/tests/test_html_comment_utils.py +0 -148
- markdown_flow-0.2.81/tests/test_markdownflow_basic.py +0 -323
- markdown_flow-0.2.81/tests/test_parser_interaction.py +0 -124
- markdown_flow-0.2.81/tests/test_parser_output.py +0 -260
- markdown_flow-0.2.81/tests/test_parser_variable.py +0 -183
- markdown_flow-0.2.81/tests/test_preprocessor.py +0 -494
- markdown_flow-0.2.81/tests/test_preserved_simple.py +0 -262
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/LICENSE +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/constants_system_prompt.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/enums.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/exceptions.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/__init__.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/classifier.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/format.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/patterns.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/stream.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/types.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/llm.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/models.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/__init__.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/code_fence_utils.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/html_comment_utils.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/interaction.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/json_parser.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/output.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/preprocessor.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/validation.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/variable.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/providers/__init__.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/providers/config.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/providers/openai.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/tag_filter.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/utils.py +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/dependency_links.txt +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/top_level.txt +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/pyproject.toml +0 -0
- {markdown_flow-0.2.81 → markdown_flow-0.2.83}/setup.cfg +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.83
|
|
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
|
|
@@ -730,7 +730,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
730
730
|
- [Ruff](https://docs.astral.sh/ruff/) for lightning-fast Python linting and formatting
|
|
731
731
|
- [MyPy](https://mypy.readthedocs.io/) for static type checking
|
|
732
732
|
- [Commitizen](https://commitizen-tools.github.io/commitizen/) for standardized commit messages
|
|
733
|
-
- [
|
|
733
|
+
- [Lefthook](https://lefthook.dev/) for automated code quality checks (git hooks + CI)
|
|
734
734
|
|
|
735
735
|
## 📞 Support
|
|
736
736
|
|
|
@@ -712,7 +712,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
712
712
|
- [Ruff](https://docs.astral.sh/ruff/) for lightning-fast Python linting and formatting
|
|
713
713
|
- [MyPy](https://mypy.readthedocs.io/) for static type checking
|
|
714
714
|
- [Commitizen](https://commitizen-tools.github.io/commitizen/) for standardized commit messages
|
|
715
|
-
- [
|
|
715
|
+
- [Lefthook](https://lefthook.dev/) for automated code quality checks (git hooks + CI)
|
|
716
716
|
|
|
717
717
|
## 📞 Support
|
|
718
718
|
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Markdown-Flow Constants
|
|
3
|
+
|
|
4
|
+
Constants for document parsing, variable matching, validation, and other core functionality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Pre-compiled regex patterns
|
|
11
|
+
COMPILED_PERCENT_VARIABLE_REGEX = re.compile(
|
|
12
|
+
r"%\{\{([^}]+)\}\}" # Match %{{variable}} format for preserved variables
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Interaction regex base patterns
|
|
16
|
+
INTERACTION_PATTERN = r"(?<!\\)\?\[([^\]]*)\](?!\()" # Base pattern with capturing group for content extraction, excludes escaped \?[]
|
|
17
|
+
INTERACTION_PATTERN_NON_CAPTURING = r"(?<!\\)\?\[[^\]]*\](?!\()" # Non-capturing version for block splitting, excludes escaped \?[]
|
|
18
|
+
INTERACTION_PATTERN_SPLIT = r"((?<!\\)\?\[[^\]]*\](?!\())" # Pattern for re.split() with outer capturing group, excludes escaped \?[]
|
|
19
|
+
|
|
20
|
+
# InteractionParser specific regex patterns
|
|
21
|
+
COMPILED_INTERACTION_REGEX = re.compile(INTERACTION_PATTERN) # Main interaction pattern matcher
|
|
22
|
+
COMPILED_LAYER1_INTERACTION_REGEX = COMPILED_INTERACTION_REGEX # Layer 1: Basic format validation (alias)
|
|
23
|
+
COMPILED_LAYER2_VARIABLE_REGEX = re.compile(r"^%\{\{([^}]+)\}\}(.*)$") # Layer 2: Variable detection
|
|
24
|
+
COMPILED_LAYER3_ELLIPSIS_REGEX = re.compile(r"^(.*)\.\.\.(.*)") # Layer 3: Split content around ellipsis
|
|
25
|
+
COMPILED_LAYER3_BUTTON_VALUE_REGEX = re.compile(r"^(.+)//(.+)$") # Layer 3: Parse Button//value format
|
|
26
|
+
COMPILED_BRACE_VARIABLE_REGEX = re.compile(
|
|
27
|
+
r"(?<!%)\{\{([^}]+)\}\}" # Match {{variable}} format for replaceable variables
|
|
28
|
+
)
|
|
29
|
+
COMPILED_SINGLE_PIPE_SPLIT_REGEX = re.compile(r"(?<!\|)\|(?!\|)") # Split on single | but not ||
|
|
30
|
+
|
|
31
|
+
# Document parsing constants (using shared INTERACTION_PATTERN defined above)
|
|
32
|
+
|
|
33
|
+
# Separators
|
|
34
|
+
BLOCK_SEPARATOR = r"\n\s*---\s*\n"
|
|
35
|
+
# Multiline preserved block fence: starts with '!' followed by 3 or more '='
|
|
36
|
+
PRESERVE_FENCE_PATTERN = r"^!={3,}\s*$"
|
|
37
|
+
COMPILED_PRESERVE_FENCE_REGEX = re.compile(PRESERVE_FENCE_PATTERN)
|
|
38
|
+
|
|
39
|
+
# Inline preserved content pattern: ===content=== format (historical compatibility)
|
|
40
|
+
INLINE_PRESERVE_PATTERN = r"^===(.+)=== *$"
|
|
41
|
+
COMPILED_INLINE_PRESERVE_REGEX = re.compile(INLINE_PRESERVE_PATTERN)
|
|
42
|
+
|
|
43
|
+
# Inline preserved content search pattern (for finding ===...=== within a line)
|
|
44
|
+
# Non-greedy match to handle multiple occurrences on same line
|
|
45
|
+
INLINE_PRESERVE_SEARCH_PATTERN = r"===\s*(.+?)\s*==="
|
|
46
|
+
COMPILED_INLINE_PRESERVE_SEARCH_REGEX = re.compile(INLINE_PRESERVE_SEARCH_PATTERN)
|
|
47
|
+
|
|
48
|
+
# Inline exclamation preserved content pattern: !===content!=== format (higher priority than INLINE_PRESERVE_PATTERN)
|
|
49
|
+
# Supports scenarios:
|
|
50
|
+
# - !===content!=== (compact format)
|
|
51
|
+
# - !=== content !=== (with spaces)
|
|
52
|
+
# - prefix !===content!=== suffix (inline mixed)
|
|
53
|
+
# - !===content\n!=== (cross-line format)
|
|
54
|
+
# Uses (?s) flag to make . match newlines, supports cross-line content
|
|
55
|
+
INLINE_EXCLAMATION_PRESERVE_PATTERN = r"(?s)!===(.*?)!==="
|
|
56
|
+
COMPILED_INLINE_EXCLAMATION_PRESERVE_REGEX = re.compile(INLINE_EXCLAMATION_PRESERVE_PATTERN)
|
|
57
|
+
|
|
58
|
+
# Code fence patterns (CommonMark specification compliant)
|
|
59
|
+
# Code block fence start: 0-3 spaces + at least 3 backticks or tildes + optional info string
|
|
60
|
+
CODE_FENCE_START_PATTERN = r"^[ ]{0,3}([`~]{3,})(.*)$"
|
|
61
|
+
COMPILED_CODE_FENCE_START_REGEX = re.compile(CODE_FENCE_START_PATTERN)
|
|
62
|
+
|
|
63
|
+
# Code block fence end: 0-3 spaces + at least 3 backticks or tildes + optional whitespace
|
|
64
|
+
CODE_FENCE_END_PATTERN = r"^[ ]{0,3}([`~]{3,})\s*$"
|
|
65
|
+
COMPILED_CODE_FENCE_END_REGEX = re.compile(CODE_FENCE_END_PATTERN)
|
|
66
|
+
|
|
67
|
+
# JSON extraction pattern for nested objects
|
|
68
|
+
# Matches JSON objects including nested structures using balanced braces
|
|
69
|
+
JSON_OBJECT_PATTERN = r"\{(?:[^{}]|(?:\{[^{}]*\}))*\}"
|
|
70
|
+
COMPILED_JSON_OBJECT_REGEX = re.compile(JSON_OBJECT_PATTERN)
|
|
71
|
+
|
|
72
|
+
# Output instruction markers
|
|
73
|
+
OUTPUT_INSTRUCTION_PREFIX = "<原样输出>"
|
|
74
|
+
OUTPUT_INSTRUCTION_SUFFIX = "</原样输出>"
|
|
75
|
+
|
|
76
|
+
# Output Language Control - Three-layer anchoring templates
|
|
77
|
+
OUTPUT_LANGUAGE_INSTRUCTION_TOP = """<output_language_override>
|
|
78
|
+
🚨 CRITICAL: 100% {0} OUTPUT REQUIRED 🚨
|
|
79
|
+
ZERO language mixing allowed. EVERY word must be in {0}.
|
|
80
|
+
Before processing: Translate ALL non-{0} words/phrases to {0} first.
|
|
81
|
+
This overrides ALL other instructions.
|
|
82
|
+
</output_language_override>"""
|
|
83
|
+
|
|
84
|
+
OUTPUT_LANGUAGE_INSTRUCTION_BOTTOM = """<output_language_final_check>
|
|
85
|
+
🚨 PRE-RESPONSE CHECK: Verify EVERY word is {0}. If ANY non-{0} word exists, translate it first. 🚨
|
|
86
|
+
</output_language_final_check>"""
|
|
87
|
+
|
|
88
|
+
# Interaction prompt templates (Modular design)
|
|
89
|
+
INTERACTION_PROMPT_BASE = """# JSON Interaction Translation Task
|
|
90
|
+
|
|
91
|
+
You will receive a JSON object containing interaction elements (`buttons` and/or `question` fields), and you need to translate the text in it into the target language.
|
|
92
|
+
|
|
93
|
+
## Output Format Requirements
|
|
94
|
+
|
|
95
|
+
- Return pure JSON only; do not add any explanations or Markdown code blocks
|
|
96
|
+
- The JSON structure must be exactly the same as the input: do not add or remove fields, do not modify key names, and do not change the order
|
|
97
|
+
- Preserve the original spaces, punctuation, and quotation marks"""
|
|
98
|
+
|
|
99
|
+
INTERACTION_PROMPT_NO_TRANSLATION = """
|
|
100
|
+
## Processing Rules
|
|
101
|
+
|
|
102
|
+
Return the input JSON exactly as-is, character for character:
|
|
103
|
+
|
|
104
|
+
- Do not translate any text
|
|
105
|
+
- Do not modify any formatting
|
|
106
|
+
- Do not add any content (such as display//value separation)
|
|
107
|
+
- Do not delete any content
|
|
108
|
+
- Do not change any order
|
|
109
|
+
|
|
110
|
+
## Example
|
|
111
|
+
|
|
112
|
+
- Input `{"buttons": ["Product Manager", "Developer"], "question": "Other role"}`
|
|
113
|
+
- Output `{"buttons": ["Product Manager", "Developer"], "question": "Other role"}`"""
|
|
114
|
+
|
|
115
|
+
INTERACTION_PROMPT_WITH_TRANSLATION = """
|
|
116
|
+
## Translation Rules
|
|
117
|
+
|
|
118
|
+
- Translate the text in `buttons` and `question` into the target language
|
|
119
|
+
- Translate only the display text (the Display part); do not change the JSON structure
|
|
120
|
+
- If `display//value` separation exists (for example `Yes//1`), translate only the Display part before `//` and keep value unchanged
|
|
121
|
+
- Use the target language 100%; language mixing is not allowed. Translate all non-target-language words before outputting
|
|
122
|
+
- If a text segment's original language is already the same as the target language, return that text unchanged (for example, when the target language is English and the original text is also English, do not rewrite it)
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
|
|
126
|
+
- Input `{"buttons": ["Yes//1", "No//0"]}`, target language Spanish -> `{"buttons": ["Sí//1", "No//0"]}`
|
|
127
|
+
- Input `{"buttons": ["Débutant", "Avancé"], "question": "Autre"}`, target language English -> `{"buttons": ["Beginner", "Advanced"], "question": "Other"}`"""
|
|
128
|
+
|
|
129
|
+
# Default: use no translation version (backward compatible)
|
|
130
|
+
DEFAULT_INTERACTION_PROMPT = INTERACTION_PROMPT_BASE + "\n" + INTERACTION_PROMPT_NO_TRANSLATION
|
|
131
|
+
|
|
132
|
+
# Interaction error prompt templates
|
|
133
|
+
DEFAULT_INTERACTION_ERROR_PROMPT = "Please rewrite the following error message to be friendlier and more personalized, helping the user understand the problem and providing constructive guidance:"
|
|
134
|
+
|
|
135
|
+
# Interaction error rendering instructions
|
|
136
|
+
INTERACTION_ERROR_RENDER_INSTRUCTIONS = """
|
|
137
|
+
Return only the friendly error message; do not include any other format or explanation."""
|
|
138
|
+
|
|
139
|
+
# Standard validation response status
|
|
140
|
+
VALIDATION_RESPONSE_OK = "ok"
|
|
141
|
+
VALIDATION_RESPONSE_ILLEGAL = "illegal"
|
|
142
|
+
|
|
143
|
+
# Validation task template (Modular design)
|
|
144
|
+
VALIDATION_TASK_BASE = """You are a string validation program, not a conversational assistant.
|
|
145
|
+
|
|
146
|
+
Your only task: check the input according to the following rules and output exactly one of these JSON objects:
|
|
147
|
+
{{"result": "ok", "parse_vars": {{"{target_variable}": "<exact user input>"}}}} or {{"result": "illegal", "reason": "<reason>"}}
|
|
148
|
+
|
|
149
|
+
It is strictly forbidden to output any natural-language explanation."""
|
|
150
|
+
|
|
151
|
+
VALIDATION_TASK_WITH_LANGUAGE = """
|
|
152
|
+
|
|
153
|
+
# reason Language Rule
|
|
154
|
+
reason must use the language specified in the <output_language_override> tag."""
|
|
155
|
+
|
|
156
|
+
VALIDATION_TASK_NO_LANGUAGE = """
|
|
157
|
+
|
|
158
|
+
# reason Language Rule
|
|
159
|
+
reason uses the primary language of the user input or question (auto-detect)."""
|
|
160
|
+
|
|
161
|
+
# Default: use no language version (backward compatible)
|
|
162
|
+
VALIDATION_TASK_TEMPLATE = VALIDATION_TASK_BASE + VALIDATION_TASK_NO_LANGUAGE
|
|
163
|
+
|
|
164
|
+
# Validation requirements template (extremely lenient version)
|
|
165
|
+
VALIDATION_REQUIREMENTS_TEMPLATE = """# Validation Algorithm (execute in order)
|
|
166
|
+
|
|
167
|
+
Step 1: Empty-value check (string length check)
|
|
168
|
+
|
|
169
|
+
Check rule: input.trim().length == 0 ?
|
|
170
|
+
- YES -> empty
|
|
171
|
+
- NO -> non-empty
|
|
172
|
+
|
|
173
|
+
Important: as long as the number of characters after trimming leading and trailing spaces is > 0, it is non-empty.
|
|
174
|
+
Important: do not judge semantics. All visible characters (a, 1, @, CJK characters) count toward length.
|
|
175
|
+
Examples:
|
|
176
|
+
- "" -> length 0 -> empty
|
|
177
|
+
- " " -> length 0 -> empty
|
|
178
|
+
- "aa" -> length 2 -> non-empty
|
|
179
|
+
- "@_@" -> length 3 -> non-empty
|
|
180
|
+
- "abc" -> length 3 -> non-empty
|
|
181
|
+
|
|
182
|
+
Step 2: Vague-answer check
|
|
183
|
+
|
|
184
|
+
Reject the following vague answers: "I don't know", "I'm not sure", "No / none", "I won't tell you"
|
|
185
|
+
|
|
186
|
+
Step 3: Religious/political check
|
|
187
|
+
|
|
188
|
+
Reject only explicit religious or political position statements (religious doctrines, political slogans, etc.).
|
|
189
|
+
Place names, regions, etc. (Beijing, Shanghai, etc.) and ordinary vocabulary do not count.
|
|
190
|
+
|
|
191
|
+
Step 4: Output result (the reason language follows the language requirement in <document_context>)
|
|
192
|
+
|
|
193
|
+
Pseudocode logic:
|
|
194
|
+
if empty:
|
|
195
|
+
output {{"result": "illegal", "reason": "input is empty (or its translation into the corresponding language)"}}
|
|
196
|
+
else if vague answer:
|
|
197
|
+
output {{"result": "illegal", "reason": "please provide specific content (or its translation into the corresponding language)"}}
|
|
198
|
+
else if religious/political:
|
|
199
|
+
output {{"result": "illegal", "reason": "contains sensitive content (or its translation into the corresponding language)"}}
|
|
200
|
+
else:
|
|
201
|
+
output {{"result": "ok", "parse_vars": {{"{target_variable}": "<exact user input>"}}}}
|
|
202
|
+
|
|
203
|
+
Extremely important:
|
|
204
|
+
- len(input after trimming leading and trailing spaces) > 0 -> it must be regarded as non-empty
|
|
205
|
+
- Symbols, numbers, brand names, place names, etc. are neither "empty" nor "invalid"
|
|
206
|
+
- Pass by default; reject only when there is an explicit violation
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
# ========== Error Message Constants ==========
|
|
210
|
+
|
|
211
|
+
# Interaction error messages
|
|
212
|
+
OPTION_SELECTION_ERROR_TEMPLATE = "请选择以下选项之一:{options}"
|
|
213
|
+
INPUT_EMPTY_ERROR = "输入不能为空"
|
|
214
|
+
|
|
215
|
+
# System error messages
|
|
216
|
+
UNSUPPORTED_PROMPT_TYPE_ERROR = "不支持的提示词类型: {prompt_type} (支持的类型: base_system, document, interaction, interaction_error, output_language)"
|
|
217
|
+
BLOCK_INDEX_OUT_OF_RANGE_ERROR = "Block index {index} is out of range; total={total}"
|
|
218
|
+
LLM_PROVIDER_REQUIRED_ERROR = "需要设置 LLMProvider 才能调用 LLM"
|
|
219
|
+
INTERACTION_PARSE_ERROR = "交互格式解析失败: {error}"
|
|
220
|
+
|
|
221
|
+
# LLM provider errors
|
|
222
|
+
NO_LLM_PROVIDER_ERROR = "NoLLMProvider 不支持 LLM 调用"
|
|
223
|
+
|
|
224
|
+
# Validation constants
|
|
225
|
+
JSON_PARSE_ERROR = "无法解析JSON响应"
|
|
226
|
+
VALIDATION_ILLEGAL_DEFAULT_REASON = "输入不合法"
|
|
227
|
+
VARIABLE_DEFAULT_VALUE = "UNKNOWN"
|
|
228
|
+
|
|
229
|
+
# Context generation constants
|
|
230
|
+
CONTEXT_QUESTION_MARKER = "# Related Question"
|
|
231
|
+
CONTEXT_CONVERSATION_MARKER = "# Conversation Context"
|
|
232
|
+
CONTEXT_BUTTON_OPTIONS_MARKER = "## Predefined Options"
|
|
233
|
+
|
|
234
|
+
# Context generation templates
|
|
235
|
+
CONTEXT_QUESTION_TEMPLATE = f"{CONTEXT_QUESTION_MARKER}\n{{question}}"
|
|
236
|
+
CONTEXT_CONVERSATION_TEMPLATE = f"{CONTEXT_CONVERSATION_MARKER}\n{{content}}"
|
|
237
|
+
CONTEXT_BUTTON_OPTIONS_TEMPLATE = (
|
|
238
|
+
f"{CONTEXT_BUTTON_OPTIONS_MARKER}\n"
|
|
239
|
+
"Available predefined options include: {button_options}\n"
|
|
240
|
+
"Note: If the user selected one of these options, it should be accepted; if the user entered custom content, accept it as long as it is a reasonable answer to the question."
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Next interaction context prompt templates
|
|
244
|
+
NEXT_INTERACTION_CONTEXT_INTRO = (
|
|
245
|
+
"The next interaction will appear immediately after this content. When generating the current content, connect to it naturally. "
|
|
246
|
+
"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."
|
|
247
|
+
)
|
|
248
|
+
NEXT_INTERACTION_TEXT_INPUT_TEMPLATE = "The next interaction asks the user to answer in text: {question}"
|
|
249
|
+
NEXT_INTERACTION_SINGLE_CHOICE_TEMPLATE = "The next interaction is a single-choice question. The user will choose one option from: {options}"
|
|
250
|
+
NEXT_INTERACTION_MULTIPLE_CHOICE_TEMPLATE = "The next interaction is a multiple-choice question. The user can choose one or more options from: {options}"
|
|
251
|
+
NEXT_INTERACTION_SINGLE_CHOICE_WITH_TEXT_INTRO = "The next interaction is a single-choice question with an optional custom text answer."
|
|
252
|
+
NEXT_INTERACTION_MULTIPLE_CHOICE_WITH_TEXT_INTRO = "The next interaction is a multiple-choice question with an optional custom text answer."
|
|
253
|
+
NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE = "The predefined options are: {options}."
|
|
254
|
+
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,
|
|
@@ -1286,11 +1294,88 @@ class MarkdownFlow:
|
|
|
1286
1294
|
if self._output_language:
|
|
1287
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}"
|
|
1288
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
|
+
|
|
1289
1301
|
# Add processed content as user message (as instruction to LLM)
|
|
1290
1302
|
messages.append({"role": "user", "content": user_content})
|
|
1291
1303
|
|
|
1292
1304
|
return messages
|
|
1293
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
|
+
|
|
1294
1379
|
def _extract_translatable_content(self, interaction_content: str) -> tuple[str, dict[str, Any] | None]:
|
|
1295
1380
|
"""Extract translatable parts from interaction content as JSON format
|
|
1296
1381
|
|
|
@@ -1550,12 +1635,12 @@ Original Error: {error_message}
|
|
|
1550
1635
|
messages.append({"role": "system", "content": render_prompt})
|
|
1551
1636
|
|
|
1552
1637
|
# Add conversation history context if provided.
|
|
1553
|
-
#
|
|
1554
|
-
#
|
|
1555
|
-
#
|
|
1556
|
-
|
|
1557
|
-
if
|
|
1558
|
-
transformed_context = self._transform_context_messages(
|
|
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)
|
|
1559
1644
|
if transformed_context:
|
|
1560
1645
|
messages.extend(transformed_context)
|
|
1561
1646
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
The following are rules you must strictly follow:
|
|
2
|
+
|
|
3
|
+
# Content Processing Rules
|
|
4
|
+
|
|
5
|
+
1. Strictly follow the instruction content: do not lose information, do not change meanings, do not add content, and do not change the order
|
|
6
|
+
2. Answer based on facts; do not fabricate details
|
|
7
|
+
3. Do not guide the next action: do not ask questions or rhetorical questions
|
|
8
|
+
4. Do not introduce yourself or greet the user unless the user requests it
|
|
9
|
+
|
|
10
|
+
# HTML Display Content Generation Rules
|
|
11
|
+
|
|
12
|
+
Enable these rules only when the user asks to generate visual content (PPT/pages/HTML/charts/images). If the user asks only to generate content, do not enable these rules.
|
|
13
|
+
|
|
14
|
+
## Rendering Mechanism
|
|
15
|
+
|
|
16
|
+
- HTML block-level elements (div/section, etc.) -> create a new screen and clear the container
|
|
17
|
+
- `<script>` / `<style>` -> append to the current screen without turning the page. They are automatically cleaned up when the page turns
|
|
18
|
+
- Text content -> output plain Markdown directly; do not wrap it in HTML tags (it must be usable for TTS reading)
|
|
19
|
+
- **Each screen defaults to a 16:9 horizontal layout**. Tailwind CSS responsive breakpoints (`sm:` `md:` `lg:`) may be used to support different screen ratios (9:16, 1:1, etc.)
|
|
20
|
+
- Text elements inside HTML must not use Markdown formatting
|
|
21
|
+
- Elements inside HTML should be as vertically compact as possible. Do not generate overly long content; the 16:9 aspect ratio must be preserved
|
|
22
|
+
|
|
23
|
+
## Style Rules
|
|
24
|
+
|
|
25
|
+
### Container And Scaling
|
|
26
|
+
|
|
27
|
+
Each screen = one fixed container that fills the viewport and must not scroll. Write the outer container as:
|
|
28
|
+
|
|
29
|
+
```text
|
|
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
|
+
<!-- content -->
|
|
32
|
+
</div>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Each HTML screen must be followed immediately by:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
<style>
|
|
39
|
+
*,*::before,*::after{box-sizing:border-box;overflow-wrap:break-word;word-wrap:break-word}
|
|
40
|
+
</style>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Fonts**:
|
|
44
|
+
|
|
45
|
+
| Purpose | style syntax |
|
|
46
|
+
| --------------- | ---------------------------------- |
|
|
47
|
+
| Cover title | font-size:3.5em; font-weight:700 |
|
|
48
|
+
| Page title | font-size:2.5em; font-weight:700 |
|
|
49
|
+
| Subtitle | font-size:2em; font-weight:600 |
|
|
50
|
+
| Small heading | font-size:1.5em; font-weight:600 |
|
|
51
|
+
| Key point title | font-size:1.25em; font-weight:500 |
|
|
52
|
+
| Body text | font-size:1em |
|
|
53
|
+
| Small text | font-size:0.85em |
|
|
54
|
+
|
|
55
|
+
### Decorative Element Rules
|
|
56
|
+
|
|
57
|
+
Decorative elements (background color blocks, glows, geometric shapes, gradient circles, dividers, etc.) must satisfy one of the following methods, and **must not occupy layout space as flowing block-level or inline elements**:
|
|
58
|
+
|
|
59
|
+
**Method A: CSS Background (preferred)**
|
|
60
|
+
Draw them directly through the container `background` / `background-image` / `linear-gradient` / `radial-gradient`, without creating separate DOM nodes.
|
|
61
|
+
|
|
62
|
+
**Method B: Floating-Layer DOM Nodes**
|
|
63
|
+
If an independent element must be used, all of the following must be satisfied:
|
|
64
|
+
|
|
65
|
+
- `position:absolute` (the parent must be `position:relative`; the outer container already satisfies this by default)
|
|
66
|
+
- `pointer-events:none` (do not intercept interactions)
|
|
67
|
+
- `z-index:0` or a negative value (place it below the main content)
|
|
68
|
+
- Positioning values `top` / `right` / `bottom` / `left` **may only use non-negative values**. Decorations must be fully contained inside the parent container's content box; do not use negative values such as `-5em` or `-3em` to make decorations protrude beyond the container boundary
|
|
69
|
+
- Width and height must not exceed 100% of the corresponding side of the parent container
|
|
70
|
+
|
|
71
|
+
**Reason**: The outer iframe synchronizes its height through `scrollHeight`. Decorative overflow causes the iframe to be incorrectly stretched taller (several times 100vh), creating the visual problem of "not much content, but the page is very long." Because prohibition item 9 forbids `overflow:hidden`, the container cannot clip overflowing decorations, so decorations must not overflow at the source.
|
|
72
|
+
|
|
73
|
+
### SVG Rules
|
|
74
|
+
|
|
75
|
+
Use HTML for text layout. Use SVG only for pure graphics (icons, arrows, connector lines). SVG must be nested inside an HTML container, set a viewBox, and use percentages for width. Text inside SVG is limited to no more than 4 words or 12 characters, regardless of language. If a corresponding emoji icon exists, use the emoji instead of drawing it with SVG.
|
|
76
|
+
|
|
77
|
+
### Preinstalled Tools
|
|
78
|
+
|
|
79
|
+
Preinstalled: Tailwind CSS v3, DaisyUI v4.12.10, GSAP v3.14.2, canvas container #ppt-container
|
|
80
|
+
**Prefer DaisyUI components and Tailwind styles**
|
|
81
|
+
|
|
82
|
+
- **For steps / ordered processes / phase plans / milestones / timelines / progress-type content, always use DaisyUI's timeline component** (`<ul class="timeline">` + `<li>`)
|
|
83
|
+
- **Do not use DaisyUI's steps component** (`.steps` / `.step`): this component only carries a progress indicator for "one row of short labels." It does not support multi-layer content such as icon + title + description. Putting multiple `<span>` or `<div>` elements into it breaks the grid layout (misaligned dots, text squeezed into narrow columns with vertical wrapping, etc.). Any "steps" requirement must use timeline
|
|
84
|
+
|
|
85
|
+
**Correct Use Of The timeline Component**
|
|
86
|
+
|
|
87
|
+
- Container: `<ul class="timeline timeline-vertical">` (use `timeline-horizontal` for horizontal layout, `timeline-compact` for compact layout). Direct child elements must be `<li>`
|
|
88
|
+
- Each `<li>` combines the following slots as needed (block-level elements may only use these classes; do not place bare `<div>` elements without classes):
|
|
89
|
+
- `<div class="timeline-start timeline-box">start-side content</div>`: left/top-side content. Adding `timeline-box` renders it as a card
|
|
90
|
+
- `<div class="timeline-middle">icon or node</div>`: centered node; place emoji/svg/number dots here
|
|
91
|
+
- `<div class="timeline-end timeline-box">end-side content</div>`: right/bottom-side content
|
|
92
|
+
- `<hr />`: line connecting adjacent `<li>` elements. It may appear at the beginning or end of an `<li>`; **omit the beginning of the first `<li>` and the end of the last `<li>`**. Write `<hr />` on both sides of every other `<li>` to form a continuous connecting line
|
|
93
|
+
- `timeline-start` and `timeline-end` may keep only one side; if the other side is empty, content displays only on the retained side
|
|
94
|
+
- Do not use oversized elements for icons or dots in `timeline-middle` (they will expand row spacing). Recommended: emoji, or fixed-size elements such as `<span class="w-6 h-6 rounded-full bg-primary text-white flex items-center justify-center">1</span>`
|
|
95
|
+
- Typical scenarios: process steps ("environment setup -> install Skill -> upload materials"), project phases (requirements -> design -> development -> launch -> operations), historical timelines, product roadmaps, milestone lists -- **all use timeline**
|
|
96
|
+
- Implement node numbers with dot elements inside `timeline-middle` (for example `<span class="w-6 h-6 rounded-full bg-primary text-white flex items-center justify-center">1</span>`). Place titles in `timeline-start` or `timeline-end`, and place descriptions immediately after the title in the same `timeline-box` (using `<br />` or two lines of `<div>` are both acceptable, because `timeline-box` itself is an independent container and is not constrained by grid)
|
|
97
|
+
|
|
98
|
+
## Operation Modes
|
|
99
|
+
|
|
100
|
+
### Create New Screen
|
|
101
|
+
|
|
102
|
+
Output an HTML block-level element -> clear the container and create a new screen.
|
|
103
|
+
|
|
104
|
+
### Append Script/Style
|
|
105
|
+
|
|
106
|
+
Output `<script>` or `<style>` -> append to the current screen without turning the page.
|
|
107
|
+
|
|
108
|
+
### Modify Existing Screen (Diff)
|
|
109
|
+
|
|
110
|
+
Use this only when the user explicitly requests modification. Format:
|
|
111
|
+
|
|
112
|
+
!+++
|
|
113
|
+
--- a/<slide_index>
|
|
114
|
+
+++ b/<slide_index>
|
|
115
|
+
@@ -<old_start>,<old_lines> +<new_start>,<new_lines> @@
|
|
116
|
+
<context>
|
|
117
|
+
-<deleted>
|
|
118
|
+
+<added>
|
|
119
|
+
!+++
|
|
120
|
+
|
|
121
|
+
Amount of modification < 50% -> Diff; >= 50% -> create a new screen.
|
|
122
|
+
|
|
123
|
+
## Prohibition List
|
|
124
|
+
|
|
125
|
+
1. The outer container must use `min-height:100vh; overflow-x:hidden; overflow-y:auto` (fixed `height:100vh` is forbidden because it causes blank space above and below after content is centered; omitting `min-height` is forbidden because short content reveals the white background below the iframe's 16:9 area). Do not set `justify-content: center / safe center` on the container (this also creates blank space above and below short content)
|
|
126
|
+
2. Do not use vmin/vmax units; use em uniformly
|
|
127
|
+
3. Do not set absolute font-size values (px/rem) on child elements; this breaks the em scaling chain
|
|
128
|
+
4. Do not manually operate #ppt-container
|
|
129
|
+
5. Do not use setTimeout
|
|
130
|
+
6. Do not output multiple consecutive block-level elements (this triggers multiple page turns); put all content inside one root element
|
|
131
|
+
7. Do not output only script and style without related HTML
|
|
132
|
+
8. Do not place `<script>` / `<style>` after text. They must appear only after `<div>`, and no plain text or Markdown text may be interleaved between them
|
|
133
|
+
9. Do not use `overflow:hidden` anywhere in any `<div>` / `<style>`
|
|
134
|
+
10. Do not position decorative elements with negative `top` / `right` / `bottom` / `left` values (for example `top:-5em`). Decorations must be fully inside the parent container
|
|
135
|
+
11. Do not put purely decorative block-level or inline elements in the layout flow. Decorations must follow the CSS background or floating-layer method in "Decorative Element Rules"
|
|
136
|
+
12. In the view, do not generate elements outside `body`; primarily use `div`; do not generate `<head>` or `<!DOCTYPE html>`
|
|
137
|
+
13. Output HTML as plain text. **Do not** use ```html or any code block marker. Do not output like this:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<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)">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: markdown-flow
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.83
|
|
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
|
|
@@ -730,7 +730,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
730
730
|
- [Ruff](https://docs.astral.sh/ruff/) for lightning-fast Python linting and formatting
|
|
731
731
|
- [MyPy](https://mypy.readthedocs.io/) for static type checking
|
|
732
732
|
- [Commitizen](https://commitizen-tools.github.io/commitizen/) for standardized commit messages
|
|
733
|
-
- [
|
|
733
|
+
- [Lefthook](https://lefthook.dev/) for automated code quality checks (git hooks + CI)
|
|
734
734
|
|
|
735
735
|
## 📞 Support
|
|
736
736
|
|
|
@@ -33,15 +33,4 @@ markdown_flow/parser/validation.py
|
|
|
33
33
|
markdown_flow/parser/variable.py
|
|
34
34
|
markdown_flow/providers/__init__.py
|
|
35
35
|
markdown_flow/providers/config.py
|
|
36
|
-
markdown_flow/providers/openai.py
|
|
37
|
-
tests/test_dynamic_interaction.py
|
|
38
|
-
tests/test_error_render_context.py
|
|
39
|
-
tests/test_formatter.py
|
|
40
|
-
tests/test_formatter_stream.py
|
|
41
|
-
tests/test_html_comment_utils.py
|
|
42
|
-
tests/test_markdownflow_basic.py
|
|
43
|
-
tests/test_parser_interaction.py
|
|
44
|
-
tests/test_parser_output.py
|
|
45
|
-
tests/test_parser_variable.py
|
|
46
|
-
tests/test_preprocessor.py
|
|
47
|
-
tests/test_preserved_simple.py
|
|
36
|
+
markdown_flow/providers/openai.py
|