markdown-flow 0.2.12__tar.gz → 0.2.28__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 (36) hide show
  1. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/PKG-INFO +18 -107
  2. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/README.md +17 -106
  3. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow/__init__.py +7 -7
  4. markdown_flow-0.2.28/markdown_flow/constants.py +359 -0
  5. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow/core.py +588 -540
  6. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow/llm.py +10 -12
  7. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow/models.py +1 -1
  8. markdown_flow-0.2.28/markdown_flow/parser/__init__.py +38 -0
  9. markdown_flow-0.2.28/markdown_flow/parser/code_fence_utils.py +190 -0
  10. markdown_flow-0.2.28/markdown_flow/parser/interaction.py +354 -0
  11. markdown_flow-0.2.28/markdown_flow/parser/json_parser.py +50 -0
  12. markdown_flow-0.2.28/markdown_flow/parser/output.py +215 -0
  13. markdown_flow-0.2.28/markdown_flow/parser/preprocessor.py +151 -0
  14. markdown_flow-0.2.28/markdown_flow/parser/validation.py +121 -0
  15. markdown_flow-0.2.28/markdown_flow/parser/variable.py +95 -0
  16. markdown_flow-0.2.28/markdown_flow/providers/__init__.py +16 -0
  17. markdown_flow-0.2.28/markdown_flow/providers/config.py +48 -0
  18. markdown_flow-0.2.28/markdown_flow/providers/openai.py +369 -0
  19. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow/utils.py +49 -51
  20. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow.egg-info/PKG-INFO +18 -107
  21. markdown_flow-0.2.28/markdown_flow.egg-info/SOURCES.txt +32 -0
  22. markdown_flow-0.2.28/tests/test_markdownflow_basic.py +232 -0
  23. markdown_flow-0.2.28/tests/test_parser_interaction.py +124 -0
  24. markdown_flow-0.2.28/tests/test_parser_variable.py +111 -0
  25. markdown_flow-0.2.28/tests/test_preprocessor.py +288 -0
  26. markdown_flow-0.2.28/tests/test_preserved_simple.py +262 -0
  27. markdown_flow-0.2.12/markdown_flow/constants.py +0 -174
  28. markdown_flow-0.2.12/markdown_flow.egg-info/SOURCES.txt +0 -16
  29. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/LICENSE +0 -0
  30. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow/enums.py +0 -0
  31. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow/exceptions.py +0 -0
  32. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow.egg-info/dependency_links.txt +0 -0
  33. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/markdown_flow.egg-info/top_level.txt +0 -0
  34. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/pyproject.toml +0 -0
  35. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/setup.cfg +0 -0
  36. {markdown_flow-0.2.12 → markdown_flow-0.2.28}/tests/test_dynamic_interaction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.12
3
+ Version: 0.2.28
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
@@ -92,36 +92,6 @@ for chunk in mf.process(
92
92
  print(chunk.content, end='')
93
93
  ```
94
94
 
95
- ### Dynamic Interaction Generation ✨
96
-
97
- Transform natural language content into interactive elements automatically:
98
-
99
- ```python
100
- from markdown_flow import MarkdownFlow, ProcessMode
101
-
102
- # Dynamic interaction generation works automatically
103
- mf = MarkdownFlow(
104
- document="询问用户的菜品偏好,并记录到变量{{菜品选择}}",
105
- llm_provider=llm_provider,
106
- document_prompt="你是中餐厅服务员,提供川菜、粤菜、鲁菜等选项"
107
- )
108
-
109
- # Process with Function Calling
110
- result = mf.process(0, ProcessMode.COMPLETE)
111
-
112
- if result.transformed_to_interaction:
113
- print(f"Generated interaction: {result.content}")
114
- # Output: ?[%{{菜品选择}} 宫保鸡丁||麻婆豆腐||水煮鱼||...其他菜品]
115
-
116
- # Continue with user input
117
- user_result = mf.process(
118
- block_index=0,
119
- mode=ProcessMode.COMPLETE,
120
- user_input={"菜品选择": ["宫保鸡丁", "麻婆豆腐"]},
121
- dynamic_interaction_format=result.content
122
- )
123
- ```
124
-
125
95
  ### Interactive Elements
126
96
 
127
97
  ```python
@@ -159,59 +129,6 @@ result = mf.process(
159
129
  )
160
130
  ```
161
131
 
162
- ## ✨ Key Features
163
-
164
- ### 🏗️ Three-Layer Architecture
165
-
166
- - **Document Level**: Parse `---` separators and `?[]` interaction patterns
167
- - **Block Level**: Categorize as CONTENT, INTERACTION, or PRESERVED_CONTENT
168
- - **Interaction Level**: Handle 6 different interaction types with smart validation
169
-
170
- ### 🔄 Dynamic Interaction Generation
171
-
172
- - **Natural Language Input**: Write content in plain language
173
- - **AI-Powered Conversion**: LLM automatically detects interaction needs using Function Calling
174
- - **Structured Data Generation**: LLM returns structured data, core builds MarkdownFlow format
175
- - **Language Agnostic**: Support for any language with proper document prompts
176
- - **Context Awareness**: Both original and resolved variable contexts provided to LLM
177
-
178
- ### 🤖 Unified LLM Integration
179
-
180
- - **Single Interface**: One `complete()` method for both regular and Function Calling modes
181
- - **Automatic Detection**: Tools parameter determines processing mode automatically
182
- - **Consistent Returns**: Always returns `LLMResult` with structured metadata
183
- - **Error Handling**: Automatic fallback from Function Calling to regular completion
184
- - **Provider Agnostic**: Abstract interface supports any LLM service
185
-
186
- ### 📝 Variable System
187
-
188
- - **Replaceable Variables**: `{{variable}}` for content personalization
189
- - **Preserved Variables**: `%{{variable}}` for LLM understanding in interactions
190
- - **Multi-Value Support**: Handle both single values and arrays
191
- - **Smart Extraction**: Automatic detection from document content
192
-
193
- ### 🎯 Interaction Types
194
-
195
- - **Text Input**: `?[%{{var}}...question]` - Free text entry
196
- - **Single Select**: `?[%{{var}} A|B|C]` - Choose one option
197
- - **Multi Select**: `?[%{{var}} A||B||C]` - Choose multiple options
198
- - **Mixed Mode**: `?[%{{var}} A||B||...custom]` - Predefined + custom input
199
- - **Display Buttons**: `?[Continue|Cancel]` - Action buttons without assignment
200
- - **Value Separation**: `?[%{{var}} Display//value|...]` - Different display/stored values
201
-
202
- ### 🔒 Content Preservation
203
-
204
- - **Multiline Format**: `!===content!===` blocks output exactly as written
205
- - **Inline Format**: `===content===` for single-line preserved content
206
- - **Variable Support**: Preserved content can contain variables for substitution
207
-
208
- ### ⚡ Performance Optimized
209
-
210
- - **Pre-compiled Regex**: All patterns compiled once for maximum performance
211
- - **Synchronous Interface**: Clean synchronous operations with optional streaming
212
- - **Stream Processing**: Real-time streaming responses supported
213
- - **Memory Efficient**: Lazy evaluation and generator patterns
214
-
215
132
  ## 📖 API Reference
216
133
 
217
134
  ### Core Classes
@@ -237,7 +154,7 @@ class MarkdownFlow:
237
154
  mode: ProcessMode = ProcessMode.COMPLETE,
238
155
  variables: Optional[Dict[str, str]] = None,
239
156
  user_input: Optional[str] = None
240
- ) -> LLMResult: ...
157
+ ) -> LLMResult | Generator[LLMResult, None, None]: ...
241
158
  ```
242
159
 
243
160
  **Methods:**
@@ -267,18 +184,13 @@ Processing mode enumeration for different use cases.
267
184
 
268
185
  ```python
269
186
  class ProcessMode(Enum):
270
- PROMPT_ONLY = "prompt_only" # Generate prompts without LLM calls
271
- COMPLETE = "complete" # Non-streaming LLM processing
272
- STREAM = "stream" # Streaming LLM responses
187
+ COMPLETE = "complete" # Non-streaming LLM processing
188
+ STREAM = "stream" # Streaming LLM responses
273
189
  ```
274
190
 
275
191
  **Usage:**
276
192
 
277
193
  ```python
278
- # Generate prompt only
279
- prompt_result = mf.process(0, ProcessMode.PROMPT_ONLY)
280
- print(prompt_result.content) # Raw prompt text
281
-
282
194
  # Complete response
283
195
  complete_result = mf.process(0, ProcessMode.COMPLETE)
284
196
  print(complete_result.content) # Full LLM response
@@ -298,10 +210,10 @@ from typing import Generator
298
210
 
299
211
  class LLMProvider(ABC):
300
212
  @abstractmethod
301
- def complete(self, prompt: str) -> LLMResult: ...
213
+ def complete(self, messages: list[dict[str, str]]) -> str: ...
302
214
 
303
215
  @abstractmethod
304
- def stream(self, prompt: str) -> Generator[str, None, None]: ...
216
+ def stream(self, messages: list[dict[str, str]]) -> Generator[str, None, None]: ...
305
217
  ```
306
218
 
307
219
  **Custom Implementation:**
@@ -311,23 +223,22 @@ class OpenAIProvider(LLMProvider):
311
223
  def __init__(self, api_key: str):
312
224
  self.client = openai.OpenAI(api_key=api_key)
313
225
 
314
- def complete(self, prompt: str) -> LLMResult:
315
- response = self.client.completions.create(
226
+ def complete(self, messages: list[dict[str, str]]) -> str:
227
+ response = self.client.chat.completions.create(
316
228
  model="gpt-3.5-turbo",
317
- prompt=prompt,
318
- max_tokens=500
229
+ messages=messages
319
230
  )
320
- return LLMResult(content=response.choices[0].text.strip())
231
+ return response.choices[0].message.content
321
232
 
322
- def stream(self, prompt: str):
323
- stream = self.client.completions.create(
233
+ def stream(self, messages: list[dict[str, str]]):
234
+ stream = self.client.chat.completions.create(
324
235
  model="gpt-3.5-turbo",
325
- prompt=prompt,
236
+ messages=messages,
326
237
  stream=True
327
238
  )
328
239
  for chunk in stream:
329
- if chunk.choices[0].text:
330
- yield chunk.choices[0].text
240
+ if chunk.choices[0].delta.content:
241
+ yield chunk.choices[0].delta.content
331
242
  ```
332
243
 
333
244
  ### Block Types
@@ -647,7 +558,7 @@ Include code samples, explanations, and practice exercises.
647
558
  mode=ProcessMode.STREAM,
648
559
  variables={
649
560
  'user_name': 'developer',
650
- 'topic': 'async programming'
561
+ 'topic': 'synchronous programming'
651
562
  }
652
563
  ):
653
564
  content += chunk.content
@@ -701,9 +612,9 @@ class InteractiveDocumentBuilder:
701
612
  self.user_responses.update(response)
702
613
 
703
614
  def handle_interaction(self, interaction_content: str):
704
- from markdown_flow.utils import InteractionParser
615
+ from markdown_flow.parser import InteractionParser
705
616
 
706
- interaction = InteractionParser.parse(interaction_content)
617
+ interaction = InteractionParser().parse(interaction_content)
707
618
  print(f"\n{interaction_content}")
708
619
 
709
620
  if interaction.name == "BUTTONS_ONLY":
@@ -74,36 +74,6 @@ for chunk in mf.process(
74
74
  print(chunk.content, end='')
75
75
  ```
76
76
 
77
- ### Dynamic Interaction Generation ✨
78
-
79
- Transform natural language content into interactive elements automatically:
80
-
81
- ```python
82
- from markdown_flow import MarkdownFlow, ProcessMode
83
-
84
- # Dynamic interaction generation works automatically
85
- mf = MarkdownFlow(
86
- document="询问用户的菜品偏好,并记录到变量{{菜品选择}}",
87
- llm_provider=llm_provider,
88
- document_prompt="你是中餐厅服务员,提供川菜、粤菜、鲁菜等选项"
89
- )
90
-
91
- # Process with Function Calling
92
- result = mf.process(0, ProcessMode.COMPLETE)
93
-
94
- if result.transformed_to_interaction:
95
- print(f"Generated interaction: {result.content}")
96
- # Output: ?[%{{菜品选择}} 宫保鸡丁||麻婆豆腐||水煮鱼||...其他菜品]
97
-
98
- # Continue with user input
99
- user_result = mf.process(
100
- block_index=0,
101
- mode=ProcessMode.COMPLETE,
102
- user_input={"菜品选择": ["宫保鸡丁", "麻婆豆腐"]},
103
- dynamic_interaction_format=result.content
104
- )
105
- ```
106
-
107
77
  ### Interactive Elements
108
78
 
109
79
  ```python
@@ -141,59 +111,6 @@ result = mf.process(
141
111
  )
142
112
  ```
143
113
 
144
- ## ✨ Key Features
145
-
146
- ### 🏗️ Three-Layer Architecture
147
-
148
- - **Document Level**: Parse `---` separators and `?[]` interaction patterns
149
- - **Block Level**: Categorize as CONTENT, INTERACTION, or PRESERVED_CONTENT
150
- - **Interaction Level**: Handle 6 different interaction types with smart validation
151
-
152
- ### 🔄 Dynamic Interaction Generation
153
-
154
- - **Natural Language Input**: Write content in plain language
155
- - **AI-Powered Conversion**: LLM automatically detects interaction needs using Function Calling
156
- - **Structured Data Generation**: LLM returns structured data, core builds MarkdownFlow format
157
- - **Language Agnostic**: Support for any language with proper document prompts
158
- - **Context Awareness**: Both original and resolved variable contexts provided to LLM
159
-
160
- ### 🤖 Unified LLM Integration
161
-
162
- - **Single Interface**: One `complete()` method for both regular and Function Calling modes
163
- - **Automatic Detection**: Tools parameter determines processing mode automatically
164
- - **Consistent Returns**: Always returns `LLMResult` with structured metadata
165
- - **Error Handling**: Automatic fallback from Function Calling to regular completion
166
- - **Provider Agnostic**: Abstract interface supports any LLM service
167
-
168
- ### 📝 Variable System
169
-
170
- - **Replaceable Variables**: `{{variable}}` for content personalization
171
- - **Preserved Variables**: `%{{variable}}` for LLM understanding in interactions
172
- - **Multi-Value Support**: Handle both single values and arrays
173
- - **Smart Extraction**: Automatic detection from document content
174
-
175
- ### 🎯 Interaction Types
176
-
177
- - **Text Input**: `?[%{{var}}...question]` - Free text entry
178
- - **Single Select**: `?[%{{var}} A|B|C]` - Choose one option
179
- - **Multi Select**: `?[%{{var}} A||B||C]` - Choose multiple options
180
- - **Mixed Mode**: `?[%{{var}} A||B||...custom]` - Predefined + custom input
181
- - **Display Buttons**: `?[Continue|Cancel]` - Action buttons without assignment
182
- - **Value Separation**: `?[%{{var}} Display//value|...]` - Different display/stored values
183
-
184
- ### 🔒 Content Preservation
185
-
186
- - **Multiline Format**: `!===content!===` blocks output exactly as written
187
- - **Inline Format**: `===content===` for single-line preserved content
188
- - **Variable Support**: Preserved content can contain variables for substitution
189
-
190
- ### ⚡ Performance Optimized
191
-
192
- - **Pre-compiled Regex**: All patterns compiled once for maximum performance
193
- - **Synchronous Interface**: Clean synchronous operations with optional streaming
194
- - **Stream Processing**: Real-time streaming responses supported
195
- - **Memory Efficient**: Lazy evaluation and generator patterns
196
-
197
114
  ## 📖 API Reference
198
115
 
199
116
  ### Core Classes
@@ -219,7 +136,7 @@ class MarkdownFlow:
219
136
  mode: ProcessMode = ProcessMode.COMPLETE,
220
137
  variables: Optional[Dict[str, str]] = None,
221
138
  user_input: Optional[str] = None
222
- ) -> LLMResult: ...
139
+ ) -> LLMResult | Generator[LLMResult, None, None]: ...
223
140
  ```
224
141
 
225
142
  **Methods:**
@@ -249,18 +166,13 @@ Processing mode enumeration for different use cases.
249
166
 
250
167
  ```python
251
168
  class ProcessMode(Enum):
252
- PROMPT_ONLY = "prompt_only" # Generate prompts without LLM calls
253
- COMPLETE = "complete" # Non-streaming LLM processing
254
- STREAM = "stream" # Streaming LLM responses
169
+ COMPLETE = "complete" # Non-streaming LLM processing
170
+ STREAM = "stream" # Streaming LLM responses
255
171
  ```
256
172
 
257
173
  **Usage:**
258
174
 
259
175
  ```python
260
- # Generate prompt only
261
- prompt_result = mf.process(0, ProcessMode.PROMPT_ONLY)
262
- print(prompt_result.content) # Raw prompt text
263
-
264
176
  # Complete response
265
177
  complete_result = mf.process(0, ProcessMode.COMPLETE)
266
178
  print(complete_result.content) # Full LLM response
@@ -280,10 +192,10 @@ from typing import Generator
280
192
 
281
193
  class LLMProvider(ABC):
282
194
  @abstractmethod
283
- def complete(self, prompt: str) -> LLMResult: ...
195
+ def complete(self, messages: list[dict[str, str]]) -> str: ...
284
196
 
285
197
  @abstractmethod
286
- def stream(self, prompt: str) -> Generator[str, None, None]: ...
198
+ def stream(self, messages: list[dict[str, str]]) -> Generator[str, None, None]: ...
287
199
  ```
288
200
 
289
201
  **Custom Implementation:**
@@ -293,23 +205,22 @@ class OpenAIProvider(LLMProvider):
293
205
  def __init__(self, api_key: str):
294
206
  self.client = openai.OpenAI(api_key=api_key)
295
207
 
296
- def complete(self, prompt: str) -> LLMResult:
297
- response = self.client.completions.create(
208
+ def complete(self, messages: list[dict[str, str]]) -> str:
209
+ response = self.client.chat.completions.create(
298
210
  model="gpt-3.5-turbo",
299
- prompt=prompt,
300
- max_tokens=500
211
+ messages=messages
301
212
  )
302
- return LLMResult(content=response.choices[0].text.strip())
213
+ return response.choices[0].message.content
303
214
 
304
- def stream(self, prompt: str):
305
- stream = self.client.completions.create(
215
+ def stream(self, messages: list[dict[str, str]]):
216
+ stream = self.client.chat.completions.create(
306
217
  model="gpt-3.5-turbo",
307
- prompt=prompt,
218
+ messages=messages,
308
219
  stream=True
309
220
  )
310
221
  for chunk in stream:
311
- if chunk.choices[0].text:
312
- yield chunk.choices[0].text
222
+ if chunk.choices[0].delta.content:
223
+ yield chunk.choices[0].delta.content
313
224
  ```
314
225
 
315
226
  ### Block Types
@@ -629,7 +540,7 @@ Include code samples, explanations, and practice exercises.
629
540
  mode=ProcessMode.STREAM,
630
541
  variables={
631
542
  'user_name': 'developer',
632
- 'topic': 'async programming'
543
+ 'topic': 'synchronous programming'
633
544
  }
634
545
  ):
635
546
  content += chunk.content
@@ -683,9 +594,9 @@ class InteractiveDocumentBuilder:
683
594
  self.user_responses.update(response)
684
595
 
685
596
  def handle_interaction(self, interaction_content: str):
686
- from markdown_flow.utils import InteractionParser
597
+ from markdown_flow.parser import InteractionParser
687
598
 
688
- interaction = InteractionParser.parse(interaction_content)
599
+ interaction = InteractionParser().parse(interaction_content)
689
600
  print(f"\n{interaction_content}")
690
601
 
691
602
  if interaction.name == "BUTTONS_ONLY":
@@ -9,7 +9,7 @@ Core Features:
9
9
  - Extract variable placeholders ({{variable}} and %{{variable}} formats)
10
10
  - Build LLM-ready prompts and message formats
11
11
  - Handle user interaction validation and input processing
12
- - Support multiple processing modes: PROMPT_ONLY, COMPLETE, STREAM
12
+ - Support multiple processing modes: COMPLETE, STREAM
13
13
 
14
14
  Supported Interaction Types:
15
15
  - TEXT_ONLY: ?[%{{var}}...question] - Text input only
@@ -32,12 +32,11 @@ Basic Usage:
32
32
  blocks = mf.get_all_blocks()
33
33
 
34
34
  # Process blocks using unified interface
35
- result = await mf.process(0, variables={'name': 'John'}, mode=ProcessMode.COMPLETE)
35
+ result = mf.process(0, variables={'name': 'John'}, mode=ProcessMode.COMPLETE)
36
36
 
37
37
  # Different processing modes
38
- prompt_result = await mf.process(0, mode=ProcessMode.PROMPT_ONLY)
39
- complete_result = await mf.process(0, mode=ProcessMode.COMPLETE)
40
- stream_result = await mf.process(0, mode=ProcessMode.STREAM)
38
+ complete_result = mf.process(0, mode=ProcessMode.COMPLETE)
39
+ stream_result = mf.process(0, mode=ProcessMode.STREAM)
41
40
 
42
41
  Variable System:
43
42
  - {{variable}} - Regular variables, replaced with actual values
@@ -53,7 +52,7 @@ Import Guide:
53
52
  from .core import MarkdownFlow
54
53
  from .enums import BlockType, InputType
55
54
  from .llm import LLMProvider, LLMResult, ProcessMode
56
- from .utils import (
55
+ from .parser import (
57
56
  InteractionParser,
58
57
  InteractionType,
59
58
  extract_interaction_question,
@@ -83,4 +82,5 @@ __all__ = [
83
82
  "replace_variables_in_text",
84
83
  ]
85
84
 
86
- __version__ = "0.2.12"
85
+ __version__ = "0.2.28"
86
+ # __version__ = "0.2.27-alpha-8"