markdown-flow 0.2.83.dev1__py3-none-any.whl → 0.2.85__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 +9 -2
- markdown_flow/constants.py +5 -1
- markdown_flow/formatter/__init__.py +2 -0
- markdown_flow/formatter/sanitize.py +95 -0
- markdown_flow/providers/openai.py +11 -8
- markdown_flow/system_prompt.md +16 -14
- {markdown_flow-0.2.83.dev1.dist-info → markdown_flow-0.2.85.dist-info}/METADATA +2 -2
- {markdown_flow-0.2.83.dev1.dist-info → markdown_flow-0.2.85.dist-info}/RECORD +11 -10
- {markdown_flow-0.2.83.dev1.dist-info → markdown_flow-0.2.85.dist-info}/WHEEL +1 -1
- {markdown_flow-0.2.83.dev1.dist-info → markdown_flow-0.2.85.dist-info}/licenses/LICENSE +0 -0
- {markdown_flow-0.2.83.dev1.dist-info → markdown_flow-0.2.85.dist-info}/top_level.txt +0 -0
markdown_flow/__init__.py
CHANGED
|
@@ -51,7 +51,13 @@ Import Guide:
|
|
|
51
51
|
# Import core classes and enums
|
|
52
52
|
from .core import MarkdownFlow
|
|
53
53
|
from .enums import BlockType, InputType
|
|
54
|
-
from .formatter import
|
|
54
|
+
from .formatter import (
|
|
55
|
+
ElementType,
|
|
56
|
+
FormattedElement,
|
|
57
|
+
StreamFormatter,
|
|
58
|
+
format_content,
|
|
59
|
+
strip_untagged_code_fences,
|
|
60
|
+
)
|
|
55
61
|
from .llm import LLMProvider, LLMResult, ProcessMode
|
|
56
62
|
from .parser import (
|
|
57
63
|
InteractionParser,
|
|
@@ -81,6 +87,7 @@ __all__ = [
|
|
|
81
87
|
"FormattedElement",
|
|
82
88
|
"StreamFormatter",
|
|
83
89
|
"format_content",
|
|
90
|
+
"strip_untagged_code_fences",
|
|
84
91
|
# Main utility functions
|
|
85
92
|
"generate_smart_validation_template",
|
|
86
93
|
"extract_interaction_question",
|
|
@@ -88,4 +95,4 @@ __all__ = [
|
|
|
88
95
|
"replace_variables_in_text",
|
|
89
96
|
]
|
|
90
97
|
|
|
91
|
-
__version__ = "0.2.
|
|
98
|
+
__version__ = "0.2.85"
|
markdown_flow/constants.py
CHANGED
|
@@ -234,7 +234,11 @@ CONTEXT_BUTTON_OPTIONS_MARKER = "## Predefined Options"
|
|
|
234
234
|
# Context generation templates
|
|
235
235
|
CONTEXT_QUESTION_TEMPLATE = f"{CONTEXT_QUESTION_MARKER}\n{{question}}"
|
|
236
236
|
CONTEXT_CONVERSATION_TEMPLATE = f"{CONTEXT_CONVERSATION_MARKER}\n{{content}}"
|
|
237
|
-
CONTEXT_BUTTON_OPTIONS_TEMPLATE =
|
|
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
|
+
)
|
|
238
242
|
|
|
239
243
|
# Next interaction context prompt templates
|
|
240
244
|
NEXT_INTERACTION_CONTEXT_INTRO = (
|
|
@@ -5,6 +5,7 @@ Provides content classification and structured output for SSE streams.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from .format import format_content
|
|
8
|
+
from .sanitize import strip_untagged_code_fences
|
|
8
9
|
from .stream import StreamFormatter
|
|
9
10
|
from .types import ClassifyResult, ElementType, FormattedElement
|
|
10
11
|
|
|
@@ -15,4 +16,5 @@ __all__ = [
|
|
|
15
16
|
"ClassifyResult",
|
|
16
17
|
"format_content",
|
|
17
18
|
"StreamFormatter",
|
|
19
|
+
"strip_untagged_code_fences",
|
|
18
20
|
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Content Sanitizer
|
|
3
|
+
|
|
4
|
+
Utilities for cleaning LLM-generated content before it is classified and
|
|
5
|
+
rendered.
|
|
6
|
+
|
|
7
|
+
Motivation
|
|
8
|
+
----------
|
|
9
|
+
LLMs intermittently wrap otherwise-renderable output (block-level HTML, SVG
|
|
10
|
+
infographics, plain narrative) in an *untagged* fenced code block, e.g. a bare
|
|
11
|
+
``` line with no info string, sometimes even leaving it unclosed. CommonMark
|
|
12
|
+
faithfully turns that region into a code block, so a renderer shows the raw
|
|
13
|
+
HTML source instead of the intended visual.
|
|
14
|
+
|
|
15
|
+
This module targets that specific artifact while preserving deliberate code
|
|
16
|
+
blocks. It relies on a simple, explicit convention: **source that is meant to
|
|
17
|
+
be displayed as code carries a language tag** (``` ```html ```, ``` ```python ```,
|
|
18
|
+
``` ```mermaid ```). An *untagged* fence therefore carries no "show the source"
|
|
19
|
+
intent and is treated as noise that can be removed so the wrapped content
|
|
20
|
+
renders normally.
|
|
21
|
+
|
|
22
|
+
The transformation is opt-in: callers apply it explicitly to generated content.
|
|
23
|
+
It does not change the behaviour of the classifier or ``format_content``.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from ..parser.code_fence_utils import is_code_fence_end, parse_code_fence_start
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _fence_info_string(fence_line: str, fence_char: str) -> str:
|
|
30
|
+
"""Return the info string (language tag) of a fence opening line."""
|
|
31
|
+
return fence_line.lstrip(" ").lstrip(fence_char).strip()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def strip_untagged_code_fences(content: str) -> str:
|
|
35
|
+
"""Remove untagged (language-less) code-fence markers from ``content``.
|
|
36
|
+
|
|
37
|
+
An untagged fence is a fence opening line whose info string is empty
|
|
38
|
+
(a bare ``` or ~~~ with no language). Such markers are dropped so the text
|
|
39
|
+
they wrapped renders as normal Markdown/HTML instead of code. This handles
|
|
40
|
+
both paired bare fences and a single unclosed/stray bare fence.
|
|
41
|
+
|
|
42
|
+
Language-tagged fences (``` ```html ```, ``` ```mermaid ```, ...) and their
|
|
43
|
+
contents are left untouched, including their bare closing fence line.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
content: Raw content, typically LLM output.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Content with untagged fence markers removed. Everything else, including
|
|
50
|
+
the lines that were inside an untagged fence, is preserved verbatim.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
>>> strip_untagged_code_fences("```\\n<div>x</div>\\n```")
|
|
54
|
+
'<div>x</div>'
|
|
55
|
+
>>> strip_untagged_code_fences("```html\\n<div>x</div>\\n```")
|
|
56
|
+
'```html\\n<div>x</div>\\n```'
|
|
57
|
+
"""
|
|
58
|
+
if not content or ("```" not in content and "~~~" not in content):
|
|
59
|
+
return content
|
|
60
|
+
|
|
61
|
+
lines = content.split("\n")
|
|
62
|
+
result: list[str] = []
|
|
63
|
+
|
|
64
|
+
# When inside a language-tagged fence we copy lines verbatim until the
|
|
65
|
+
# matching closing fence, so nested bare backticks and the closing fence
|
|
66
|
+
# itself are preserved.
|
|
67
|
+
inside_tagged_fence = False
|
|
68
|
+
open_fence = None
|
|
69
|
+
|
|
70
|
+
for line in lines:
|
|
71
|
+
if inside_tagged_fence:
|
|
72
|
+
result.append(line)
|
|
73
|
+
if open_fence is not None and is_code_fence_end(line, open_fence):
|
|
74
|
+
inside_tagged_fence = False
|
|
75
|
+
open_fence = None
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
fence = parse_code_fence_start(line)
|
|
79
|
+
if fence is None:
|
|
80
|
+
result.append(line)
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
info = _fence_info_string(fence.line, fence.char)
|
|
84
|
+
if info == "":
|
|
85
|
+
# Untagged fence at the top level: drop the marker line, keep the
|
|
86
|
+
# content it wrapped. Paired and unclosed bare fences both collapse
|
|
87
|
+
# here because we never enter a fenced state for them.
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# Language-tagged fence: keep it and preserve everything up to its close.
|
|
91
|
+
result.append(line)
|
|
92
|
+
inside_tagged_fence = True
|
|
93
|
+
open_fence = fence
|
|
94
|
+
|
|
95
|
+
return "\n".join(result)
|
|
@@ -7,7 +7,7 @@ token tracking, and comprehensive metadata.
|
|
|
7
7
|
|
|
8
8
|
import time
|
|
9
9
|
from collections.abc import Generator
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any, cast
|
|
11
11
|
|
|
12
12
|
from ..llm import LLMProvider
|
|
13
13
|
from .config import ProviderConfig
|
|
@@ -82,7 +82,7 @@ class OpenAIProvider(LLMProvider):
|
|
|
82
82
|
self._print_request_info(messages, actual_model, actual_temperature)
|
|
83
83
|
|
|
84
84
|
# Format messages
|
|
85
|
-
formatted_messages = self._format_messages(messages)
|
|
85
|
+
formatted_messages = cast(Any, self._format_messages(messages))
|
|
86
86
|
|
|
87
87
|
# Record start time
|
|
88
88
|
start_time = time.time()
|
|
@@ -168,18 +168,21 @@ class OpenAIProvider(LLMProvider):
|
|
|
168
168
|
self._print_request_info(messages, actual_model, actual_temperature)
|
|
169
169
|
|
|
170
170
|
# Format messages
|
|
171
|
-
formatted_messages = self._format_messages(messages)
|
|
171
|
+
formatted_messages = cast(Any, self._format_messages(messages))
|
|
172
172
|
|
|
173
173
|
# Record start time
|
|
174
174
|
start_time = time.time()
|
|
175
175
|
|
|
176
176
|
try:
|
|
177
177
|
# Create streaming response
|
|
178
|
-
stream =
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
stream = cast(
|
|
179
|
+
Any,
|
|
180
|
+
self.client.chat.completions.create(
|
|
181
|
+
model=actual_model,
|
|
182
|
+
messages=formatted_messages,
|
|
183
|
+
temperature=actual_temperature,
|
|
184
|
+
stream=True,
|
|
185
|
+
),
|
|
183
186
|
)
|
|
184
187
|
|
|
185
188
|
for chunk in stream:
|
markdown_flow/system_prompt.md
CHANGED
|
@@ -2,14 +2,15 @@ The following are rules you must strictly follow:
|
|
|
2
2
|
|
|
3
3
|
# Content Processing Rules
|
|
4
4
|
|
|
5
|
-
1.
|
|
6
|
-
2.
|
|
7
|
-
3. Do not
|
|
8
|
-
4. Do not
|
|
5
|
+
1. All user messages are instructions to follow, not questions to answer
|
|
6
|
+
2. Strictly follow the instructions: do not lose information, do not change meanings, do not add content, do not fabricate details, and do not change the order
|
|
7
|
+
3. Do not respond with conversational filler or acknowledgments such as "Ok", "Good", or "Sure". Directly output the requested content
|
|
8
|
+
4. Do not guide the next action: do not ask questions or rhetorical questions
|
|
9
|
+
5. Do not introduce yourself or greet the user unless the user requests it
|
|
9
10
|
|
|
10
11
|
# HTML Display Content Generation Rules
|
|
11
12
|
|
|
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
|
|
13
|
+
Enable these rules only when the user asks to generate visual content (Slides/PPT/pages/HTML/charts/images). If the user asks only to generate text, do not enable these rules.
|
|
13
14
|
|
|
14
15
|
## Rendering Mechanism
|
|
15
16
|
|
|
@@ -44,13 +45,13 @@ Each HTML screen must be followed immediately by:
|
|
|
44
45
|
|
|
45
46
|
| Purpose | style syntax |
|
|
46
47
|
| --------------- | ---------------------------------- |
|
|
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
|
|
48
|
+
| Cover title | font-size:3.5em; font-weight:700 |
|
|
49
|
+
| Page title | font-size:2.5em; font-weight:700 |
|
|
50
|
+
| Subtitle | font-size:2em; font-weight:600 |
|
|
51
|
+
| Small heading | font-size:1.5em; font-weight:600 |
|
|
52
|
+
| Key point title | font-size:1.25em; font-weight:500 |
|
|
53
|
+
| Body text | font-size:1em |
|
|
54
|
+
| Small text | font-size:0.85em |
|
|
54
55
|
|
|
55
56
|
### Decorative Element Rules
|
|
56
57
|
|
|
@@ -72,7 +73,7 @@ If an independent element must be used, all of the following must be satisfied:
|
|
|
72
73
|
|
|
73
74
|
### SVG Rules
|
|
74
75
|
|
|
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
|
|
76
|
+
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
|
|
|
77
78
|
### Preinstalled Tools
|
|
78
79
|
|
|
@@ -137,4 +138,5 @@ Amount of modification < 50% -> Diff; >= 50% -> create a new screen.
|
|
|
137
138
|
13. Output HTML as plain text. **Do not** use ```html or any code block marker. Do not output like this:
|
|
138
139
|
|
|
139
140
|
```html
|
|
140
|
-
|
|
141
|
+
html code here
|
|
142
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: markdown-flow
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.85
|
|
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
|
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
markdown_flow/__init__.py,sha256=
|
|
2
|
-
markdown_flow/constants.py,sha256=
|
|
1
|
+
markdown_flow/__init__.py,sha256=jtGp5FMSeh0aR20vLURjwQQE7oGstRly9PcRuHIYSLU,3052
|
|
2
|
+
markdown_flow/constants.py,sha256=1Plp8ihjfz3F1KJ6PCs6xy_nzlbD96n4OQyizEcL2rs,12772
|
|
3
3
|
markdown_flow/constants_system_prompt.py,sha256=mUUNdQn0-_VnmeuFnOv-5Rdi8kGKTNsZACu4MAdo0bo,489
|
|
4
4
|
markdown_flow/core.py,sha256=dZlZY8kJV43wQma4ishtHt9AC_hlIi1Q8VHjv1CJIpI,66529
|
|
5
5
|
markdown_flow/enums.py,sha256=Wr41zt0Ce5b3fyLtOTE2erEVp1n92g9OVaBF_BZg_l8,820
|
|
6
6
|
markdown_flow/exceptions.py,sha256=9sUZ-Jd3CPLdSRqG8Pw7eMm7cv_S3VZM6jmjUU8OhIc,976
|
|
7
7
|
markdown_flow/llm.py,sha256=XH27meLtwcYigAPTJaX6fQYYup5HEb7MFZKAFsKV0Qg,2888
|
|
8
8
|
markdown_flow/models.py,sha256=EJtZ-EQffKHmNbKJXVsHxGXHV0ywOgdAzopWDSjyVmM,2417
|
|
9
|
-
markdown_flow/system_prompt.md,sha256=
|
|
9
|
+
markdown_flow/system_prompt.md,sha256=GMoknW6knqjo9OQhs6Lh-S7C20q4iSQ2YJmZFJMM-kg,9763
|
|
10
10
|
markdown_flow/tag_filter.py,sha256=1LSXY9Q40QFdVK0FsqOtxZEikqNb3PkLkjYhkVfYxoA,2542
|
|
11
11
|
markdown_flow/utils.py,sha256=sI4cNDgJlM81ntj0iIAQkbMarDwta4wQB6kljVos9Js,28779
|
|
12
|
-
markdown_flow/formatter/__init__.py,sha256=
|
|
12
|
+
markdown_flow/formatter/__init__.py,sha256=r2vuvRpqN8aAC7hCgZfeEwsrYgFRWRPdOvcRrahjtAA,444
|
|
13
13
|
markdown_flow/formatter/classifier.py,sha256=N_jLghaSPjS-T4gHC3GTpuxvVKJlaUC3DyfmXL-jwAU,9256
|
|
14
14
|
markdown_flow/formatter/format.py,sha256=TXd4T2W8STC84e9Rjq0QWgMsfFrlJFYa45mpsYFs7DY,3090
|
|
15
15
|
markdown_flow/formatter/patterns.py,sha256=zvz_J7ALuGp_mvfHcswWHLNP9L7fbv0Z1g7oDmD5Vdc,2231
|
|
16
|
+
markdown_flow/formatter/sanitize.py,sha256=GUbbmSCcJp9L1RO-srwSgPEIY3pNwyue8hc-M-yzKp8,3686
|
|
16
17
|
markdown_flow/formatter/stream.py,sha256=TpGJKpht0Cm7j4p0lLvpbl_uTAq7dZ_gS7VEVF5WZYM,5266
|
|
17
18
|
markdown_flow/formatter/types.py,sha256=d92iTBZK8r7hir5dIv1vIGzX6YE7-pb1fh6DIT7FfnE,1329
|
|
18
19
|
markdown_flow/parser/__init__.py,sha256=UtezItde4BC3g2b06SCxiBI-F8yhgj01lc_iPIzARPo,1184
|
|
@@ -26,9 +27,9 @@ markdown_flow/parser/validation.py,sha256=egx-cEoaLXjzTaYCD03fGxvK8yvPUzXZV26bE6
|
|
|
26
27
|
markdown_flow/parser/variable.py,sha256=3UwDSacqlXi7zfRm2ZA5D7h8fFMIL68iV6uFNqu-F90,5562
|
|
27
28
|
markdown_flow/providers/__init__.py,sha256=QMr8H9gxoLr6pWXoAb11oZX_She6KWPxnRips537nQ4,319
|
|
28
29
|
markdown_flow/providers/config.py,sha256=NT6Sy_8j7Yy2zYwjEQ1XsG3NC_L_ABeQg2DJnct5QsE,3356
|
|
29
|
-
markdown_flow/providers/openai.py,sha256=
|
|
30
|
-
markdown_flow-0.2.
|
|
31
|
-
markdown_flow-0.2.
|
|
32
|
-
markdown_flow-0.2.
|
|
33
|
-
markdown_flow-0.2.
|
|
34
|
-
markdown_flow-0.2.
|
|
30
|
+
markdown_flow/providers/openai.py,sha256=09w1ruYzVOavtP7uE-osfEdAr97Fyjkr8gaGLqkgUrg,12434
|
|
31
|
+
markdown_flow-0.2.85.dist-info/licenses/LICENSE,sha256=qz3BziejhHPd1xa5eVtYEU5Qp6L2pn4otuj194uGxmc,1069
|
|
32
|
+
markdown_flow-0.2.85.dist-info/METADATA,sha256=jVoeCh_bXLaUvsvXRss-XpOrdgpD2OxaHzR2xXlUW7s,20965
|
|
33
|
+
markdown_flow-0.2.85.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
34
|
+
markdown_flow-0.2.85.dist-info/top_level.txt,sha256=DpigGvQuIt2L0TTLnDU5sylhiTGiZS7MmAMa2hi-AJs,14
|
|
35
|
+
markdown_flow-0.2.85.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|