使用MarkdownIt库拆分Markdown文本
在处理大模型的返回结果过程中,有些时候大模型返回的文本过长,甚至超过Telegram消息的长度限制。
这个时候,就需要对消息进行拆分。
但是,不能简单地根据分片长度或者换行符(\n
)暴力拆分,因为这样可能会破坏Markdown
的格式,特别是当返回中有代码片段(code fence
)
时。因为代码中存在换行符,故会被拆成2部分,导致代码片段的闭合符```
影响后续的文本格式。
本例是一个拆分长`Markdown并保留原来格式的示例。
其利用了MarkdownIt
库,这个库能够将Markdown
文本解析为token
,每个token
都有基本的属性,观察属性,可以总结规律。
基本的拆分原则是:
- 不在type为
open
的token处拆分 - 不对
ul
和li
进行拆分 - 不对
code fence
进行拆分
以下是不完美代码示例:
1from markdown_it import MarkdownIt
2# from markdown_it.renderer import RendererProtocol
3from typing import List
4
5# 利用 markdown-it 库解析 markdown 文本
6
7# 示例用法
8markdown_text = (
9 """
10好的,下面我将用一个简单的 Java 示例来解释饿汉式单例模式的缺点。
11
12首先,我们来看一个典型的饿汉式单例模式的实现:
13
14```java
15public class EagerSingleton {
16
17 private static final EagerSingleton instance = new EagerSingleton();
18
19 private EagerSingleton() {
20 // 私有构造函数,防止外部实例化
21 System.out.println("EagerSingleton is initialized."); // 用于观察初始化时机
22 }
23
24 public static EagerSingleton getInstance() {
25 return instance;
26 }
27
28 public void doSomething() {
29 System.out.println("Doing something...");
30 }
31
32 public static void main(String[] args) {
33 EagerSingleton.getInstance().doSomething();
34 }
35}
36```
37
38在这个例子中,`instance` 静态变量在类加载时就被立即初始化了。 即使你的程序在启动时并不需要使用这个单例,它也会被创建。 这就是饿汉式的主要缺点:
39
40**缺点:资源浪费**
41
42* **过早初始化:** 无论是否需要,单例实例都会在类加载时被创建。 如果这个单例对象的创建过程比较耗时,或者占用的资源较多,而程序在某些情况下根本不需要用到它,那么就会造成不必要的资源浪费。
43* **无法延迟加载:** 饿汉式无法实现延迟加载(lazy loading)。 延迟加载指的是只有在真正需要使用对象时才创建它。
44
45**示例说明:**
46
47在上面的代码中,`System.out.println("EagerSingleton is initialized.");` 这行代码会在类加载时立即执行,表明单例对象被创建。 即使你只运行程序,但没有调用 `getInstance()` 方法,单例对象仍然会被创建。
48
49**何时不适合使用饿汉式:**
50
51* 当单例对象的创建非常耗时或占用大量资源时。
52
53 * 这是2级列表
54 * This is 2nd class List
55
56* 当无法确定程序启动时是否一定会用到该单例对象时。
57
58**总结:**
59
60饿汉式单例模式实现简单,线程安全,但在某些情况下可能会造成资源浪费。 如果你确定程序启动时一定会用到该单例对象,并且创建过程不复杂,那么饿汉式是一个不错的选择。 否则,可以考虑使用懒汉式或其他单例模式的变体,以实现延迟加载。
61
62为了更清楚地说明问题,可以考虑以下场景:
63
64假设 `EagerSingleton` 类需要连接到一个数据库,而这个数据库连接的建立需要花费较长时间。 如果程序在启动后的一段时间内并不需要访问数据库,那么使用饿汉式就会导致数据库连接过早建立,浪费资源。
65
66希望这个解释能够帮助你理解饿汉式单例模式的缺点。
67
68 """
69)
70
71markdown_text_2 = (
72 """
73为了更准确地满足你的需求,请告诉我你对哪个方向的 Python 应用更感兴趣,例如 Web 开发、数据分析、人工智能等。这样我可以为你提供更具针对性的学习建议和资源。
74非常乐意为您整理一份Python学习大纲。以下是一个更精简、更侧重实用性的学习路径,适合希望快速上手并应用于实际项目的学习者:
75
76**第一阶段:Python快速入门**
77
781. **基础语法**
79 * 变量、数据类型(字符串、数字、布尔值、列表、字典)
80 * 运算符、表达式
81 * 输入输出
822. **流程控制**
83 * 条件语句(if/else)
84 * 循环语句(for/while)
853. **函数**
86 * 定义函数、调用函数
87 * 参数传递
88 * 返回值
894. **常用数据结构**
90 * 列表(List):增删改查、切片
91 * 字典(Dictionary):键值对操作
925. **模块**
93 * 导入模块(import)
94 * 常用标准库(如`os`, `datetime`, `random`)
95
96**第二阶段:面向对象编程**
97
981. **类与对象**
99 * 定义类、创建对象
100 * 属性和方法
101 * `self`关键字
1022. **继承**
103 * 单继承
104 * 方法重写
1053. **简单实践**
106 * 编写简单的类来解决实际问题
107
108**第三阶段:常用库与应用**
109
1101. **数据处理**
111 * Pandas:数据读取、清洗、分析
1122. **Web开发**
113 * Flask:搭建简单Web应用
1143. **爬虫**
115 * Requests:发送HTTP请求
116 * Beautiful Soup:解析HTML
1174. **数据库**
118 * SQLite:基本数据库操作
119
120**第四阶段:项目实践**
121
1221. **选择项目**
123 * 根据兴趣选择小项目(如:简单爬虫、Web应用、数据分析)
1242. **完成项目**
125 * 从头到尾完成项目,遇到问题查阅资料
1263. **代码优化**
127 * 学习代码规范,优化代码结构
128
129**学习资源**
130
131* **在线平台**:
132 * Codecademy, Coursera, Udemy
133* **书籍**:
134 * 《Python Crash Course》
135 * 《Automate the Boring Stuff with Python》
136* **官方文档**:
137 * Python官方网站
138
139**学习建议**
140
141* **动手实践**:边学边练,多写代码
142* **解决问题**:遇到问题积极搜索、提问
143* **持续学习**:Python生态丰富,不断学习新库和技术
144
145这个大纲更注重实用性和快速上手,可以帮助您在较短时间内掌握Python核心技能,并应用于实际项目中。如果您对某个领域(如Web开发、数据分析)特别感兴趣,可以深入学习相关库和框架。
146
147为了更好地帮助您,请告诉我您对哪个方向的Python应用更感兴趣?例如,Web开发、数据分析、自动化脚本等。这样我可以为您提供更具体的学习建议和资源。
148 """
149)
150
151# markdown_text 和markdown_text_2解析结果是一样的
152md = MarkdownIt("commonmark", {"html": False, "typographer": True})
153tokens = md.parse(markdown_text_2)
154
155# for i, token in enumerate(tokens):
156# print(f"token{i}({token.type}):\n {token.content}")
157
158chunks = []
159chunk = ""
160max_chunk_length = 10000
161last_token_tag = ""
162
163for token in tokens:
164 last_token_tag = token.tag
165 """
166 分片长度限制 20 但是要保留完整的markdown格式。
167 chunk 总是以 open开头 close结束
168 code fence只有一个token 其他内容每一行至少有3个token(open inline close)
169 列表项内容至少外层还有2个token包围(list_item_open list_item_close)
170 列表顶层还有2个token(bullet_list_open bullet_list_close)
171 保证列表的完整性
172 保证code fence的完整性
173 """
174 if token.type.endswith("_open"):
175 if token.tag == 'ul':
176 chunk += "\n"
177 elif token.tag == 'li':
178 if token.level > 1:
179 chunk += "\t" + token.info + token.markup + " "
180 else:
181 chunk += token.info + token.markup + " "
182 elif last_token_tag == 'li':
183 continue
184 else:
185 # 判断是否结束token?
186 if len(chunk) > max_chunk_length:
187 # 开新的chunk
188 chunks.append(chunk)
189 chunk = ""
190
191 elif token.type == "fence":
192 chunk += token.markup + token.info + "\n" + token.content + token.markup + "\n\n"
193 elif token.type.endswith("_close"):
194 if token.tag == 'ul':
195 chunk += "\n"
196 else:
197 chunk += token.content
198 # nested ul li
199 if token.level > 1:
200 chunk += "\n"
201 else:
202 chunk += "\n\n"
203else:
204 chunks.append(chunk)
205
206print(f'chunks size: {len(chunks)}')
207for i, c in enumerate(chunks):
208 print(f'chunk[{i}]: {c}')