markdown-flow 0.2.18__py3-none-any.whl → 0.2.23__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.
Potentially problematic release.
This version of markdown-flow might be problematic. Click here for more details.
- markdown_flow/__init__.py +1 -1
- markdown_flow/constants.py +52 -22
- markdown_flow/core.py +256 -36
- markdown_flow/llm.py +4 -2
- markdown_flow/utils.py +12 -1
- {markdown_flow-0.2.18.dist-info → markdown_flow-0.2.23.dist-info}/METADATA +1 -1
- markdown_flow-0.2.23.dist-info/RECORD +13 -0
- markdown_flow-0.2.18.dist-info/RECORD +0 -13
- {markdown_flow-0.2.18.dist-info → markdown_flow-0.2.23.dist-info}/WHEEL +0 -0
- {markdown_flow-0.2.18.dist-info → markdown_flow-0.2.23.dist-info}/licenses/LICENSE +0 -0
- {markdown_flow-0.2.18.dist-info → markdown_flow-0.2.23.dist-info}/top_level.txt +0 -0
markdown_flow/__init__.py
CHANGED
markdown_flow/constants.py
CHANGED
|
@@ -91,13 +91,32 @@ VALIDATION_RESPONSE_ILLEGAL = "illegal"
|
|
|
91
91
|
|
|
92
92
|
# Output instruction processing
|
|
93
93
|
OUTPUT_INSTRUCTION_EXPLANATION = f"""<preserve_or_translate_instruction>
|
|
94
|
-
|
|
94
|
+
# ⚠️ 最高优先级规则
|
|
95
|
+
|
|
96
|
+
**{OUTPUT_INSTRUCTION_PREFIX}{OUTPUT_INSTRUCTION_SUFFIX} 标记之间的内容是用户必须看到的最终输出内容,不是指令!**
|
|
97
|
+
|
|
98
|
+
关键要点:
|
|
99
|
+
1. **这些内容必须出现在你的回复中** - 即使其他提示词说"不要回应指令"也不适用于此
|
|
100
|
+
2. **绝对不要输出标记本身** - 只输出标记之间的实际内容
|
|
101
|
+
3. **默认逐字原样输出** - 不要改写、润色或优化,保持原文不变
|
|
102
|
+
4. **唯一例外是跨语言翻译** - 仅当需要将内容从一种语言翻译成另一种语言时才可翻译
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
<critical_understanding>
|
|
107
|
+
重要理解:
|
|
108
|
+
- {OUTPUT_INSTRUCTION_PREFIX}{OUTPUT_INSTRUCTION_SUFFIX} 中的内容不是"指令"或"执行要求"
|
|
109
|
+
- 即使内容看起来像标题、提示或说明,也必须原样输出给用户
|
|
110
|
+
- 这条规则的优先级高于文档中的其他任何提示词
|
|
111
|
+
- 其他提示词说的"不要回应指令"、"不要展示指令"等,不适用于此标记内的内容
|
|
112
|
+
</critical_understanding>
|
|
95
113
|
|
|
96
114
|
<default_behavior>
|
|
97
115
|
默认行为: 完全保持原样输出
|
|
98
116
|
- 标记之间的内容必须逐字原样输出
|
|
99
117
|
- 严禁改写、润色、优化或调整任何表达方式
|
|
100
118
|
- 严禁添加、删除或替换任何文字
|
|
119
|
+
- 即使内容是标题格式(如 ## 标题)也必须原样输出
|
|
101
120
|
</default_behavior>
|
|
102
121
|
|
|
103
122
|
<exception_rule>
|
|
@@ -107,25 +126,33 @@ OUTPUT_INSTRUCTION_EXPLANATION = f"""<preserve_or_translate_instruction>
|
|
|
107
126
|
- 如果内容无需翻译,则绝对不允许做任何改动
|
|
108
127
|
</exception_rule>
|
|
109
128
|
|
|
110
|
-
<output_requirement>
|
|
111
|
-
输出要求:
|
|
112
|
-
- 不要输出{OUTPUT_INSTRUCTION_PREFIX}{OUTPUT_INSTRUCTION_SUFFIX}标记本身
|
|
113
|
-
- 只输出标记之间的实际内容
|
|
114
|
-
</output_requirement>
|
|
115
|
-
|
|
116
129
|
<examples>
|
|
117
|
-
示例1 -
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
示例2 -
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
示例3 -
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
✅ 示例1 - 正确: 保持原样且不输出标记:
|
|
131
|
+
输入: {OUTPUT_INSTRUCTION_PREFIX}**下面我们做个练习。**{OUTPUT_INSTRUCTION_SUFFIX}
|
|
132
|
+
正确输出: **下面我们做个练习。**
|
|
133
|
+
|
|
134
|
+
✅ 示例2 - 正确: 标题也要原样输出:
|
|
135
|
+
输入: {OUTPUT_INSTRUCTION_PREFIX}## 专属指南 for 用户{OUTPUT_INSTRUCTION_SUFFIX}
|
|
136
|
+
正确输出: ## 专属指南 for 用户
|
|
137
|
+
|
|
138
|
+
✅ 示例3 - 正确: 语言翻译且不输出标记:
|
|
139
|
+
输入: {OUTPUT_INSTRUCTION_PREFIX}**Let's do an exercise.**{OUTPUT_INSTRUCTION_SUFFIX}
|
|
140
|
+
正确输出: **让我们做个练习。**
|
|
141
|
+
|
|
142
|
+
❌ 示例4 - 错误: 输出了XML标记:
|
|
143
|
+
输入: {OUTPUT_INSTRUCTION_PREFIX}## 标题内容{OUTPUT_INSTRUCTION_SUFFIX}
|
|
144
|
+
错误输出: {OUTPUT_INSTRUCTION_PREFIX}## 标题内容{OUTPUT_INSTRUCTION_SUFFIX}
|
|
145
|
+
错误原因: 不应该输出标记本身!
|
|
146
|
+
|
|
147
|
+
❌ 示例5 - 错误: 同语言改写:
|
|
148
|
+
输入: {OUTPUT_INSTRUCTION_PREFIX}**下面我们做个练习。**{OUTPUT_INSTRUCTION_SUFFIX}
|
|
149
|
+
错误输出: **来,咱们做个有趣的小练习**
|
|
150
|
+
错误原因: 擅自改写了中文内容
|
|
151
|
+
|
|
152
|
+
❌ 示例6 - 错误: 没有输出固定内容:
|
|
153
|
+
输入: {OUTPUT_INSTRUCTION_PREFIX}## 攻略|专属指南{OUTPUT_INSTRUCTION_SUFFIX}
|
|
154
|
+
错误输出: (什么都不输出,或者跳过这部分)
|
|
155
|
+
错误原因: 必须输出标记之间的内容!
|
|
129
156
|
</examples>
|
|
130
157
|
</preserve_or_translate_instruction>
|
|
131
158
|
|
|
@@ -145,9 +172,10 @@ SMART_VALIDATION_TEMPLATE = """# 任务
|
|
|
145
172
|
# 提取要求
|
|
146
173
|
1. 仔细阅读上述相关问题,理解这个问题想要获取什么信息
|
|
147
174
|
2. 从用户回答中提取与该问题相关的信息
|
|
148
|
-
3.
|
|
149
|
-
4.
|
|
150
|
-
5.
|
|
175
|
+
3. 如果提供了预定义选项,用户选择这些选项时都应该接受;自定义输入应与选项主题相关
|
|
176
|
+
4. 对于昵称/姓名类问题,任何非空的合理字符串(包括简短的如"ee"、"aa"、"007"等)都应该接受
|
|
177
|
+
5. 只有当用户回答完全无关、包含不当内容或明显不合理时才标记为不合法
|
|
178
|
+
6. 确保提取的信息准确、完整且符合预期格式"""
|
|
151
179
|
|
|
152
180
|
# Validation template for buttons with text input
|
|
153
181
|
BUTTONS_WITH_TEXT_VALIDATION_TEMPLATE = """用户针对以下问题进行了输入:
|
|
@@ -193,7 +221,9 @@ VARIABLE_DEFAULT_VALUE = "UNKNOWN"
|
|
|
193
221
|
# Context generation constants
|
|
194
222
|
CONTEXT_QUESTION_MARKER = "# 相关问题"
|
|
195
223
|
CONTEXT_CONVERSATION_MARKER = "# 对话上下文"
|
|
224
|
+
CONTEXT_BUTTON_OPTIONS_MARKER = "## 预定义选项"
|
|
196
225
|
|
|
197
226
|
# Context generation templates
|
|
198
227
|
CONTEXT_QUESTION_TEMPLATE = f"{CONTEXT_QUESTION_MARKER}\n{{question}}"
|
|
199
228
|
CONTEXT_CONVERSATION_TEMPLATE = f"{CONTEXT_CONVERSATION_MARKER}\n{{content}}"
|
|
229
|
+
CONTEXT_BUTTON_OPTIONS_TEMPLATE = f"{CONTEXT_BUTTON_OPTIONS_MARKER}\n可选的预定义选项包括:{{button_options}}\n注意:用户如果选择了这些选项,都应该接受;如果输入了自定义内容,应检查是否与选项主题相关。"
|
markdown_flow/core.py
CHANGED
|
@@ -60,6 +60,7 @@ class MarkdownFlow:
|
|
|
60
60
|
_document_prompt: str | None
|
|
61
61
|
_interaction_prompt: str | None
|
|
62
62
|
_interaction_error_prompt: str | None
|
|
63
|
+
_max_context_length: int
|
|
63
64
|
_blocks: list[Block] | None
|
|
64
65
|
_interaction_configs: dict[int, InteractionValidationConfig]
|
|
65
66
|
|
|
@@ -70,6 +71,7 @@ class MarkdownFlow:
|
|
|
70
71
|
document_prompt: str | None = None,
|
|
71
72
|
interaction_prompt: str | None = None,
|
|
72
73
|
interaction_error_prompt: str | None = None,
|
|
74
|
+
max_context_length: int = 0,
|
|
73
75
|
):
|
|
74
76
|
"""
|
|
75
77
|
Initialize MarkdownFlow instance.
|
|
@@ -80,12 +82,14 @@ class MarkdownFlow:
|
|
|
80
82
|
document_prompt: Document-level system prompt
|
|
81
83
|
interaction_prompt: Interaction content rendering prompt
|
|
82
84
|
interaction_error_prompt: Interaction error rendering prompt
|
|
85
|
+
max_context_length: Maximum number of context messages to keep (0 = unlimited)
|
|
83
86
|
"""
|
|
84
87
|
self._document = document
|
|
85
88
|
self._llm_provider = llm_provider
|
|
86
89
|
self._document_prompt = document_prompt
|
|
87
90
|
self._interaction_prompt = interaction_prompt or DEFAULT_INTERACTION_PROMPT
|
|
88
91
|
self._interaction_error_prompt = interaction_error_prompt or DEFAULT_INTERACTION_ERROR_PROMPT
|
|
92
|
+
self._max_context_length = max_context_length
|
|
89
93
|
self._blocks = None
|
|
90
94
|
self._interaction_configs: dict[int, InteractionValidationConfig] = {}
|
|
91
95
|
|
|
@@ -110,6 +114,44 @@ class MarkdownFlow:
|
|
|
110
114
|
else:
|
|
111
115
|
raise ValueError(UNSUPPORTED_PROMPT_TYPE_ERROR.format(prompt_type=prompt_type))
|
|
112
116
|
|
|
117
|
+
def _truncate_context(
|
|
118
|
+
self,
|
|
119
|
+
context: list[dict[str, str]] | None,
|
|
120
|
+
) -> list[dict[str, str]] | None:
|
|
121
|
+
"""
|
|
122
|
+
Filter and truncate context to specified maximum length.
|
|
123
|
+
|
|
124
|
+
Processing steps:
|
|
125
|
+
1. Filter out messages with empty content (empty string or whitespace only)
|
|
126
|
+
2. Truncate to max_context_length if configured (0 = unlimited)
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
context: Original context list
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Filtered and truncated context. Returns None if no valid messages remain.
|
|
133
|
+
"""
|
|
134
|
+
if not context:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
# Step 1: Filter out messages with empty or whitespace-only content
|
|
138
|
+
filtered_context = [msg for msg in context if msg.get("content", "").strip()]
|
|
139
|
+
|
|
140
|
+
# Return None if no valid messages remain after filtering
|
|
141
|
+
if not filtered_context:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
# Step 2: Truncate to max_context_length if configured
|
|
145
|
+
if self._max_context_length == 0:
|
|
146
|
+
# No limit, return all filtered messages
|
|
147
|
+
return filtered_context
|
|
148
|
+
|
|
149
|
+
# Keep the most recent N messages
|
|
150
|
+
if len(filtered_context) > self._max_context_length:
|
|
151
|
+
return filtered_context[-self._max_context_length :]
|
|
152
|
+
|
|
153
|
+
return filtered_context
|
|
154
|
+
|
|
113
155
|
@property
|
|
114
156
|
def document(self) -> str:
|
|
115
157
|
"""Get document content."""
|
|
@@ -210,7 +252,7 @@ class MarkdownFlow:
|
|
|
210
252
|
if block.block_type == BlockType.INTERACTION:
|
|
211
253
|
if user_input is None:
|
|
212
254
|
# Render interaction content
|
|
213
|
-
return self._process_interaction_render(block_index, mode, variables)
|
|
255
|
+
return self._process_interaction_render(block_index, mode, context, variables)
|
|
214
256
|
# Process user input
|
|
215
257
|
return self._process_interaction_input(block_index, user_input, mode, context, variables)
|
|
216
258
|
|
|
@@ -231,8 +273,11 @@ class MarkdownFlow:
|
|
|
231
273
|
variables: dict[str, str | list[str]] | None,
|
|
232
274
|
):
|
|
233
275
|
"""Process content block."""
|
|
234
|
-
#
|
|
235
|
-
|
|
276
|
+
# Truncate context to configured maximum length
|
|
277
|
+
truncated_context = self._truncate_context(context)
|
|
278
|
+
|
|
279
|
+
# Build messages with context
|
|
280
|
+
messages = self._build_content_messages(block_index, variables, truncated_context)
|
|
236
281
|
|
|
237
282
|
if mode == ProcessMode.PROMPT_ONLY:
|
|
238
283
|
return LLMResult(prompt=messages[-1]["content"], metadata={"messages": messages})
|
|
@@ -266,7 +311,13 @@ class MarkdownFlow:
|
|
|
266
311
|
|
|
267
312
|
return LLMResult(content=content)
|
|
268
313
|
|
|
269
|
-
def _process_interaction_render(
|
|
314
|
+
def _process_interaction_render(
|
|
315
|
+
self,
|
|
316
|
+
block_index: int,
|
|
317
|
+
mode: ProcessMode,
|
|
318
|
+
context: list[dict[str, str]] | None = None,
|
|
319
|
+
variables: dict[str, str | list[str]] | None = None,
|
|
320
|
+
):
|
|
270
321
|
"""Process interaction content rendering."""
|
|
271
322
|
block = self.get_block(block_index)
|
|
272
323
|
|
|
@@ -283,8 +334,11 @@ class MarkdownFlow:
|
|
|
283
334
|
# Unable to extract, return processed content
|
|
284
335
|
return LLMResult(content=processed_block.content)
|
|
285
336
|
|
|
286
|
-
#
|
|
287
|
-
|
|
337
|
+
# Truncate context to configured maximum length
|
|
338
|
+
truncated_context = self._truncate_context(context)
|
|
339
|
+
|
|
340
|
+
# Build render messages with context
|
|
341
|
+
messages = self._build_interaction_render_messages(question_text, truncated_context)
|
|
288
342
|
|
|
289
343
|
if mode == ProcessMode.PROMPT_ONLY:
|
|
290
344
|
return LLMResult(
|
|
@@ -356,7 +410,7 @@ class MarkdownFlow:
|
|
|
356
410
|
# Basic validation
|
|
357
411
|
if not user_input or not any(values for values in user_input.values()):
|
|
358
412
|
error_msg = INPUT_EMPTY_ERROR
|
|
359
|
-
return self._render_error(error_msg, mode)
|
|
413
|
+
return self._render_error(error_msg, mode, context)
|
|
360
414
|
|
|
361
415
|
# Get the target variable value from user_input
|
|
362
416
|
target_values = user_input.get(target_variable, [])
|
|
@@ -370,24 +424,103 @@ class MarkdownFlow:
|
|
|
370
424
|
|
|
371
425
|
if "error" in parse_result:
|
|
372
426
|
error_msg = INTERACTION_PARSE_ERROR.format(error=parse_result["error"])
|
|
373
|
-
return self._render_error(error_msg, mode)
|
|
427
|
+
return self._render_error(error_msg, mode, context)
|
|
374
428
|
|
|
375
429
|
interaction_type = parse_result.get("type")
|
|
376
430
|
|
|
377
431
|
# Process user input based on interaction type
|
|
378
432
|
if interaction_type in [
|
|
379
|
-
InteractionType.BUTTONS_ONLY,
|
|
380
433
|
InteractionType.BUTTONS_WITH_TEXT,
|
|
381
|
-
InteractionType.BUTTONS_MULTI_SELECT,
|
|
382
434
|
InteractionType.BUTTONS_MULTI_WITH_TEXT,
|
|
383
435
|
]:
|
|
384
|
-
#
|
|
436
|
+
# Buttons with text input: smart validation (match buttons first, then LLM validate custom text)
|
|
437
|
+
buttons = parse_result.get("buttons", [])
|
|
438
|
+
|
|
439
|
+
# Step 1: Match button values
|
|
440
|
+
matched_values, unmatched_values = self._match_button_values(buttons, target_values)
|
|
441
|
+
|
|
442
|
+
# Step 2: If there are unmatched values (custom text), validate with LLM
|
|
443
|
+
if unmatched_values:
|
|
444
|
+
# Create user_input for LLM validation (only custom text)
|
|
445
|
+
custom_input = {target_variable: unmatched_values}
|
|
446
|
+
|
|
447
|
+
validation_result = self._process_llm_validation(
|
|
448
|
+
block_index=block_index,
|
|
449
|
+
user_input=custom_input,
|
|
450
|
+
target_variable=target_variable,
|
|
451
|
+
mode=mode,
|
|
452
|
+
context=context,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Handle validation result based on mode
|
|
456
|
+
if mode == ProcessMode.PROMPT_ONLY:
|
|
457
|
+
# Return validation prompt
|
|
458
|
+
return validation_result
|
|
459
|
+
|
|
460
|
+
if mode == ProcessMode.COMPLETE:
|
|
461
|
+
# Check if validation passed
|
|
462
|
+
if isinstance(validation_result, LLMResult) and validation_result.variables:
|
|
463
|
+
validated_values = validation_result.variables.get(target_variable, [])
|
|
464
|
+
# Merge matched button values + validated custom text
|
|
465
|
+
all_values = matched_values + validated_values
|
|
466
|
+
return LLMResult(
|
|
467
|
+
content="",
|
|
468
|
+
variables={target_variable: all_values},
|
|
469
|
+
metadata={
|
|
470
|
+
"interaction_type": str(interaction_type),
|
|
471
|
+
"matched_button_values": matched_values,
|
|
472
|
+
"validated_custom_values": validated_values,
|
|
473
|
+
},
|
|
474
|
+
)
|
|
475
|
+
else:
|
|
476
|
+
# Validation failed, return error
|
|
477
|
+
return validation_result
|
|
478
|
+
|
|
479
|
+
if mode == ProcessMode.STREAM:
|
|
480
|
+
# For stream mode, collect validation result
|
|
481
|
+
def stream_merge_generator():
|
|
482
|
+
# Consume the validation stream
|
|
483
|
+
for result in validation_result: # type: ignore[attr-defined]
|
|
484
|
+
if isinstance(result, LLMResult) and result.variables:
|
|
485
|
+
validated_values = result.variables.get(target_variable, [])
|
|
486
|
+
all_values = matched_values + validated_values
|
|
487
|
+
yield LLMResult(
|
|
488
|
+
content="",
|
|
489
|
+
variables={target_variable: all_values},
|
|
490
|
+
metadata={
|
|
491
|
+
"interaction_type": str(interaction_type),
|
|
492
|
+
"matched_button_values": matched_values,
|
|
493
|
+
"validated_custom_values": validated_values,
|
|
494
|
+
},
|
|
495
|
+
)
|
|
496
|
+
else:
|
|
497
|
+
# Validation failed
|
|
498
|
+
yield result
|
|
499
|
+
|
|
500
|
+
return stream_merge_generator()
|
|
501
|
+
else:
|
|
502
|
+
# All values matched buttons, return directly
|
|
503
|
+
return LLMResult(
|
|
504
|
+
content="",
|
|
505
|
+
variables={target_variable: matched_values},
|
|
506
|
+
metadata={
|
|
507
|
+
"interaction_type": str(interaction_type),
|
|
508
|
+
"all_matched_buttons": True,
|
|
509
|
+
},
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
if interaction_type in [
|
|
513
|
+
InteractionType.BUTTONS_ONLY,
|
|
514
|
+
InteractionType.BUTTONS_MULTI_SELECT,
|
|
515
|
+
]:
|
|
516
|
+
# Pure button types: only basic button validation (no LLM)
|
|
385
517
|
return self._process_button_validation(
|
|
386
518
|
parse_result,
|
|
387
519
|
target_values,
|
|
388
520
|
target_variable,
|
|
389
521
|
mode,
|
|
390
522
|
interaction_type,
|
|
523
|
+
context,
|
|
391
524
|
)
|
|
392
525
|
|
|
393
526
|
if interaction_type == InteractionType.NON_ASSIGNMENT_BUTTON:
|
|
@@ -403,19 +536,50 @@ class MarkdownFlow:
|
|
|
403
536
|
)
|
|
404
537
|
|
|
405
538
|
# Text-only input type: ?[%{{sys_user_nickname}}...question]
|
|
406
|
-
#
|
|
539
|
+
# Use LLM validation to check if input is relevant to the question
|
|
407
540
|
if target_values:
|
|
408
|
-
return
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
"values": target_values,
|
|
415
|
-
},
|
|
541
|
+
return self._process_llm_validation(
|
|
542
|
+
block_index=block_index,
|
|
543
|
+
user_input=user_input,
|
|
544
|
+
target_variable=target_variable,
|
|
545
|
+
mode=mode,
|
|
546
|
+
context=context,
|
|
416
547
|
)
|
|
417
548
|
error_msg = f"No input provided for variable '{target_variable}'"
|
|
418
|
-
return self._render_error(error_msg, mode)
|
|
549
|
+
return self._render_error(error_msg, mode, context)
|
|
550
|
+
|
|
551
|
+
def _match_button_values(
|
|
552
|
+
self,
|
|
553
|
+
buttons: list[dict[str, str]],
|
|
554
|
+
target_values: list[str],
|
|
555
|
+
) -> tuple[list[str], list[str]]:
|
|
556
|
+
"""
|
|
557
|
+
Match user input values against button options.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
buttons: List of button dictionaries with 'display' and 'value' keys
|
|
561
|
+
target_values: User input values to match
|
|
562
|
+
|
|
563
|
+
Returns:
|
|
564
|
+
Tuple of (matched_values, unmatched_values)
|
|
565
|
+
- matched_values: Values that match button options (using button value)
|
|
566
|
+
- unmatched_values: Values that don't match any button
|
|
567
|
+
"""
|
|
568
|
+
matched_values = []
|
|
569
|
+
unmatched_values = []
|
|
570
|
+
|
|
571
|
+
for value in target_values:
|
|
572
|
+
matched = False
|
|
573
|
+
for button in buttons:
|
|
574
|
+
if value in [button["display"], button["value"]]:
|
|
575
|
+
matched_values.append(button["value"]) # Use button value
|
|
576
|
+
matched = True
|
|
577
|
+
break
|
|
578
|
+
|
|
579
|
+
if not matched:
|
|
580
|
+
unmatched_values.append(value)
|
|
581
|
+
|
|
582
|
+
return matched_values, unmatched_values
|
|
419
583
|
|
|
420
584
|
def _process_button_validation(
|
|
421
585
|
self,
|
|
@@ -424,6 +588,7 @@ class MarkdownFlow:
|
|
|
424
588
|
target_variable: str,
|
|
425
589
|
mode: ProcessMode,
|
|
426
590
|
interaction_type: InteractionType,
|
|
591
|
+
context: list[dict[str, str]] | None = None,
|
|
427
592
|
) -> LLMResult | Generator[LLMResult, None, None]:
|
|
428
593
|
"""
|
|
429
594
|
Simplified button validation with new input format.
|
|
@@ -434,6 +599,7 @@ class MarkdownFlow:
|
|
|
434
599
|
target_variable: Target variable name
|
|
435
600
|
mode: Processing mode
|
|
436
601
|
interaction_type: Type of interaction
|
|
602
|
+
context: Conversation history context (optional)
|
|
437
603
|
"""
|
|
438
604
|
buttons = parse_result.get("buttons", [])
|
|
439
605
|
is_multi_select = interaction_type in [
|
|
@@ -459,7 +625,7 @@ class MarkdownFlow:
|
|
|
459
625
|
# Pure button mode requires input
|
|
460
626
|
button_displays = [btn["display"] for btn in buttons]
|
|
461
627
|
error_msg = f"Please select from: {', '.join(button_displays)}"
|
|
462
|
-
return self._render_error(error_msg, mode)
|
|
628
|
+
return self._render_error(error_msg, mode, context)
|
|
463
629
|
|
|
464
630
|
# Validate input values against available buttons
|
|
465
631
|
valid_values = []
|
|
@@ -484,7 +650,7 @@ class MarkdownFlow:
|
|
|
484
650
|
if invalid_values and not allow_text_input:
|
|
485
651
|
button_displays = [btn["display"] for btn in buttons]
|
|
486
652
|
error_msg = f"Invalid options: {', '.join(invalid_values)}. Please select from: {', '.join(button_displays)}"
|
|
487
|
-
return self._render_error(error_msg, mode)
|
|
653
|
+
return self._render_error(error_msg, mode, context)
|
|
488
654
|
|
|
489
655
|
# Success: return validated values
|
|
490
656
|
return LLMResult(
|
|
@@ -505,10 +671,11 @@ class MarkdownFlow:
|
|
|
505
671
|
user_input: dict[str, list[str]],
|
|
506
672
|
target_variable: str,
|
|
507
673
|
mode: ProcessMode,
|
|
674
|
+
context: list[dict[str, str]] | None = None,
|
|
508
675
|
) -> LLMResult | Generator[LLMResult, None, None]:
|
|
509
676
|
"""Process LLM validation."""
|
|
510
677
|
# Build validation messages
|
|
511
|
-
messages = self._build_validation_messages(block_index, user_input, target_variable)
|
|
678
|
+
messages = self._build_validation_messages(block_index, user_input, target_variable, context)
|
|
512
679
|
|
|
513
680
|
if mode == ProcessMode.PROMPT_ONLY:
|
|
514
681
|
return LLMResult(
|
|
@@ -612,9 +779,18 @@ class MarkdownFlow:
|
|
|
612
779
|
|
|
613
780
|
return stream_generator()
|
|
614
781
|
|
|
615
|
-
def _render_error(
|
|
782
|
+
def _render_error(
|
|
783
|
+
self,
|
|
784
|
+
error_message: str,
|
|
785
|
+
mode: ProcessMode,
|
|
786
|
+
context: list[dict[str, str]] | None = None,
|
|
787
|
+
) -> LLMResult | Generator[LLMResult, None, None]:
|
|
616
788
|
"""Render user-friendly error message."""
|
|
617
|
-
|
|
789
|
+
# Truncate context to configured maximum length
|
|
790
|
+
truncated_context = self._truncate_context(context)
|
|
791
|
+
|
|
792
|
+
# Build error messages with context
|
|
793
|
+
messages = self._build_error_render_messages(error_message, truncated_context)
|
|
618
794
|
|
|
619
795
|
if mode == ProcessMode.PROMPT_ONLY:
|
|
620
796
|
return LLMResult(
|
|
@@ -645,6 +821,7 @@ class MarkdownFlow:
|
|
|
645
821
|
self,
|
|
646
822
|
block_index: int,
|
|
647
823
|
variables: dict[str, str | list[str]] | None,
|
|
824
|
+
context: list[dict[str, str]] | None = None,
|
|
648
825
|
) -> list[dict[str, str]]:
|
|
649
826
|
"""Build content block messages."""
|
|
650
827
|
block = self.get_block(block_index)
|
|
@@ -671,18 +848,22 @@ class MarkdownFlow:
|
|
|
671
848
|
# No document prompt but has preserved content, add explanation alone
|
|
672
849
|
messages.append({"role": "system", "content": OUTPUT_INSTRUCTION_EXPLANATION.strip()})
|
|
673
850
|
|
|
674
|
-
#
|
|
675
|
-
#
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
851
|
+
# Add conversation history context if provided
|
|
852
|
+
# Context is inserted after system message and before current user message
|
|
853
|
+
truncated_context = self._truncate_context(context)
|
|
854
|
+
if truncated_context:
|
|
855
|
+
messages.extend(truncated_context)
|
|
679
856
|
|
|
680
857
|
# Add processed content as user message (as instruction to LLM)
|
|
681
858
|
messages.append({"role": "user", "content": block_content})
|
|
682
859
|
|
|
683
860
|
return messages
|
|
684
861
|
|
|
685
|
-
def _build_interaction_render_messages(
|
|
862
|
+
def _build_interaction_render_messages(
|
|
863
|
+
self,
|
|
864
|
+
question_text: str,
|
|
865
|
+
context: list[dict[str, str]] | None = None,
|
|
866
|
+
) -> list[dict[str, str]]:
|
|
686
867
|
"""Build interaction rendering messages."""
|
|
687
868
|
# Check if using custom interaction prompt
|
|
688
869
|
if self._interaction_prompt != DEFAULT_INTERACTION_PROMPT:
|
|
@@ -696,15 +877,32 @@ class MarkdownFlow:
|
|
|
696
877
|
messages = []
|
|
697
878
|
|
|
698
879
|
messages.append({"role": "system", "content": render_prompt})
|
|
880
|
+
|
|
881
|
+
# NOTE: Context is temporarily disabled for interaction rendering
|
|
882
|
+
# Mixing conversation history with interaction content rewriting can cause issues
|
|
883
|
+
# The context parameter is kept in the signature for future use
|
|
884
|
+
# truncated_context = self._truncate_context(context)
|
|
885
|
+
# if truncated_context:
|
|
886
|
+
# messages.extend(truncated_context)
|
|
887
|
+
|
|
699
888
|
messages.append({"role": "user", "content": question_text})
|
|
700
889
|
|
|
701
890
|
return messages
|
|
702
891
|
|
|
703
|
-
def _build_validation_messages(
|
|
892
|
+
def _build_validation_messages(
|
|
893
|
+
self,
|
|
894
|
+
block_index: int,
|
|
895
|
+
user_input: dict[str, list[str]],
|
|
896
|
+
target_variable: str,
|
|
897
|
+
context: list[dict[str, str]] | None = None,
|
|
898
|
+
) -> list[dict[str, str]]:
|
|
704
899
|
"""Build validation messages."""
|
|
705
900
|
block = self.get_block(block_index)
|
|
706
901
|
config = self.get_interaction_validation_config(block_index)
|
|
707
902
|
|
|
903
|
+
# Truncate context to configured maximum length
|
|
904
|
+
truncated_context = self._truncate_context(context)
|
|
905
|
+
|
|
708
906
|
if config and config.validation_template:
|
|
709
907
|
# Use custom validation template
|
|
710
908
|
validation_prompt = config.validation_template
|
|
@@ -716,6 +914,7 @@ class MarkdownFlow:
|
|
|
716
914
|
else:
|
|
717
915
|
# Use smart default validation template
|
|
718
916
|
from .utils import (
|
|
917
|
+
InteractionParser,
|
|
719
918
|
extract_interaction_question,
|
|
720
919
|
generate_smart_validation_template,
|
|
721
920
|
)
|
|
@@ -723,11 +922,17 @@ class MarkdownFlow:
|
|
|
723
922
|
# Extract interaction question
|
|
724
923
|
interaction_question = extract_interaction_question(block.content)
|
|
725
924
|
|
|
726
|
-
#
|
|
925
|
+
# Parse interaction to extract button information
|
|
926
|
+
parser = InteractionParser()
|
|
927
|
+
parse_result = parser.parse(block.content)
|
|
928
|
+
buttons = parse_result.get("buttons") if "buttons" in parse_result else None
|
|
929
|
+
|
|
930
|
+
# Generate smart validation template with context and buttons
|
|
727
931
|
validation_template = generate_smart_validation_template(
|
|
728
932
|
target_variable,
|
|
729
|
-
context=
|
|
933
|
+
context=truncated_context,
|
|
730
934
|
interaction_question=interaction_question,
|
|
935
|
+
buttons=buttons,
|
|
731
936
|
)
|
|
732
937
|
|
|
733
938
|
# Replace template variables
|
|
@@ -740,6 +945,11 @@ class MarkdownFlow:
|
|
|
740
945
|
messages = []
|
|
741
946
|
|
|
742
947
|
messages.append({"role": "system", "content": system_message})
|
|
948
|
+
|
|
949
|
+
# Add conversation history context if provided (only if not using custom template)
|
|
950
|
+
if truncated_context and not (config and config.validation_template):
|
|
951
|
+
messages.extend(truncated_context)
|
|
952
|
+
|
|
743
953
|
messages.append({"role": "user", "content": validation_prompt})
|
|
744
954
|
|
|
745
955
|
return messages
|
|
@@ -770,7 +980,11 @@ class MarkdownFlow:
|
|
|
770
980
|
|
|
771
981
|
return messages
|
|
772
982
|
|
|
773
|
-
def _build_error_render_messages(
|
|
983
|
+
def _build_error_render_messages(
|
|
984
|
+
self,
|
|
985
|
+
error_message: str,
|
|
986
|
+
context: list[dict[str, str]] | None = None,
|
|
987
|
+
) -> list[dict[str, str]]:
|
|
774
988
|
"""Build error rendering messages."""
|
|
775
989
|
render_prompt = f"""{self._interaction_error_prompt}
|
|
776
990
|
|
|
@@ -783,6 +997,12 @@ Original Error: {error_message}
|
|
|
783
997
|
messages.append({"role": "system", "content": self._document_prompt})
|
|
784
998
|
|
|
785
999
|
messages.append({"role": "system", "content": render_prompt})
|
|
1000
|
+
|
|
1001
|
+
# Add conversation history context if provided
|
|
1002
|
+
truncated_context = self._truncate_context(context)
|
|
1003
|
+
if truncated_context:
|
|
1004
|
+
messages.extend(truncated_context)
|
|
1005
|
+
|
|
786
1006
|
messages.append({"role": "user", "content": error_message})
|
|
787
1007
|
|
|
788
1008
|
return messages
|
markdown_flow/llm.py
CHANGED
|
@@ -43,7 +43,8 @@ class LLMProvider(ABC):
|
|
|
43
43
|
Non-streaming LLM call.
|
|
44
44
|
|
|
45
45
|
Args:
|
|
46
|
-
messages: Message list in format [{"role": "system/user/assistant", "content": "..."}]
|
|
46
|
+
messages: Message list in format [{"role": "system/user/assistant", "content": "..."}].
|
|
47
|
+
This list already includes conversation history context merged by MarkdownFlow.
|
|
47
48
|
|
|
48
49
|
Returns:
|
|
49
50
|
str: LLM response content
|
|
@@ -58,7 +59,8 @@ class LLMProvider(ABC):
|
|
|
58
59
|
Streaming LLM call.
|
|
59
60
|
|
|
60
61
|
Args:
|
|
61
|
-
messages: Message list in format [{"role": "system/user/assistant", "content": "..."}]
|
|
62
|
+
messages: Message list in format [{"role": "system/user/assistant", "content": "..."}].
|
|
63
|
+
This list already includes conversation history context merged by MarkdownFlow.
|
|
62
64
|
|
|
63
65
|
Yields:
|
|
64
66
|
str: Incremental LLM response content
|
markdown_flow/utils.py
CHANGED
|
@@ -19,6 +19,7 @@ from .constants import (
|
|
|
19
19
|
COMPILED_PERCENT_VARIABLE_REGEX,
|
|
20
20
|
COMPILED_PRESERVE_FENCE_REGEX,
|
|
21
21
|
COMPILED_SINGLE_PIPE_SPLIT_REGEX,
|
|
22
|
+
CONTEXT_BUTTON_OPTIONS_TEMPLATE,
|
|
22
23
|
CONTEXT_CONVERSATION_TEMPLATE,
|
|
23
24
|
CONTEXT_QUESTION_MARKER,
|
|
24
25
|
CONTEXT_QUESTION_TEMPLATE,
|
|
@@ -479,6 +480,7 @@ def generate_smart_validation_template(
|
|
|
479
480
|
target_variable: str,
|
|
480
481
|
context: list[dict[str, Any]] | None = None,
|
|
481
482
|
interaction_question: str | None = None,
|
|
483
|
+
buttons: list[dict[str, str]] | None = None,
|
|
482
484
|
) -> str:
|
|
483
485
|
"""
|
|
484
486
|
Generate smart validation template based on context and question.
|
|
@@ -487,19 +489,28 @@ def generate_smart_validation_template(
|
|
|
487
489
|
target_variable: Target variable name
|
|
488
490
|
context: Context message list with role and content fields
|
|
489
491
|
interaction_question: Question text from interaction block
|
|
492
|
+
buttons: Button options list with display and value fields
|
|
490
493
|
|
|
491
494
|
Returns:
|
|
492
495
|
Generated validation template
|
|
493
496
|
"""
|
|
494
497
|
# Build context information
|
|
495
498
|
context_info = ""
|
|
496
|
-
if interaction_question or context:
|
|
499
|
+
if interaction_question or context or buttons:
|
|
497
500
|
context_parts = []
|
|
498
501
|
|
|
499
502
|
# Add question information (most important, put first)
|
|
500
503
|
if interaction_question:
|
|
501
504
|
context_parts.append(CONTEXT_QUESTION_TEMPLATE.format(question=interaction_question))
|
|
502
505
|
|
|
506
|
+
# Add button options information
|
|
507
|
+
if buttons:
|
|
508
|
+
button_displays = [btn.get("display", "") for btn in buttons if btn.get("display")]
|
|
509
|
+
if button_displays:
|
|
510
|
+
button_options_str = ", ".join(button_displays)
|
|
511
|
+
button_info = CONTEXT_BUTTON_OPTIONS_TEMPLATE.format(button_options=button_options_str)
|
|
512
|
+
context_parts.append(button_info)
|
|
513
|
+
|
|
503
514
|
# Add conversation context
|
|
504
515
|
if context:
|
|
505
516
|
for msg in context:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: markdown-flow
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
markdown_flow/__init__.py,sha256=yOjV6AHQGRI53Lrwrxv6HextaliQ2iTIk5TsTMRA2aM,2851
|
|
2
|
+
markdown_flow/constants.py,sha256=yJH6Ao8Y4Z_fceWG6XkMrKLqLCkpyTFFJYCi_npLY3k,10629
|
|
3
|
+
markdown_flow/core.py,sha256=TE5u-oukMMWkkLB6f-K91EbNYqcgZX1nFBmNlAi3Prc,41346
|
|
4
|
+
markdown_flow/enums.py,sha256=Wr41zt0Ce5b3fyLtOTE2erEVp1n92g9OVaBF_BZg_l8,820
|
|
5
|
+
markdown_flow/exceptions.py,sha256=9sUZ-Jd3CPLdSRqG8Pw7eMm7cv_S3VZM6jmjUU8OhIc,976
|
|
6
|
+
markdown_flow/llm.py,sha256=OjW0MC_Q3uDAuOBtlI7s1_oG-WDpaVXpm5daoeVcy40,2306
|
|
7
|
+
markdown_flow/models.py,sha256=ENcvXMVXwpFN-RzbeVHhXTjBN0bbmRpJ96K-XS2rizI,2893
|
|
8
|
+
markdown_flow/utils.py,sha256=eSsiqorPOMhagCvDSlw7QJs64D16DHvN5nlK-nu3MzI,29076
|
|
9
|
+
markdown_flow-0.2.23.dist-info/licenses/LICENSE,sha256=qz3BziejhHPd1xa5eVtYEU5Qp6L2pn4otuj194uGxmc,1069
|
|
10
|
+
markdown_flow-0.2.23.dist-info/METADATA,sha256=-QGmXAjgIu61kPSzQKeYTKLpZiW3m4lzyT5o_tGCIG4,21010
|
|
11
|
+
markdown_flow-0.2.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
markdown_flow-0.2.23.dist-info/top_level.txt,sha256=DpigGvQuIt2L0TTLnDU5sylhiTGiZS7MmAMa2hi-AJs,14
|
|
13
|
+
markdown_flow-0.2.23.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
markdown_flow/__init__.py,sha256=5TBCmuAdWvPqKJHpP5_R2qVOGf4FFkdaL6oUazBIY7E,2851
|
|
2
|
-
markdown_flow/constants.py,sha256=HI061nHbGG9BeN-n9dMX17GlAT7fmYmsRZ6Cr8OSbXY,8809
|
|
3
|
-
markdown_flow/core.py,sha256=Z0c5SssgPhqbDhbO2HZgHAaX6RpJEccb_r9RoGHVEjI,32565
|
|
4
|
-
markdown_flow/enums.py,sha256=Wr41zt0Ce5b3fyLtOTE2erEVp1n92g9OVaBF_BZg_l8,820
|
|
5
|
-
markdown_flow/exceptions.py,sha256=9sUZ-Jd3CPLdSRqG8Pw7eMm7cv_S3VZM6jmjUU8OhIc,976
|
|
6
|
-
markdown_flow/llm.py,sha256=E2aq-OXwt4rS-alpf_iIJd2K38De_O3pzSZHuEaMeoE,2100
|
|
7
|
-
markdown_flow/models.py,sha256=ENcvXMVXwpFN-RzbeVHhXTjBN0bbmRpJ96K-XS2rizI,2893
|
|
8
|
-
markdown_flow/utils.py,sha256=rJOalKxCGuXYiAJzI3WfD-loLc-7BHQGpac934_uC4c,28504
|
|
9
|
-
markdown_flow-0.2.18.dist-info/licenses/LICENSE,sha256=qz3BziejhHPd1xa5eVtYEU5Qp6L2pn4otuj194uGxmc,1069
|
|
10
|
-
markdown_flow-0.2.18.dist-info/METADATA,sha256=-y3oljzO7iSaHHodM8c4id3SRMDxdP3zhSpihSUYW0I,21010
|
|
11
|
-
markdown_flow-0.2.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
markdown_flow-0.2.18.dist-info/top_level.txt,sha256=DpigGvQuIt2L0TTLnDU5sylhiTGiZS7MmAMa2hi-AJs,14
|
|
13
|
-
markdown_flow-0.2.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|