在lora训练大语言模型的时候,需要严格的qa语料集,但是我们一般的文章内容并不是提问和回答,这时候我们就需要人为的对文章进行分割和提问。这种方式十分耗费人力物力。我开发了一套将文章自动拆分成语料集的ai工作流。
我们用到内容为dify,ollama,java 程序
dify工作流

将yaml文件导入到工作流中
app:
description: ''
icon: 🤖
icon_background: '#FFEAD5'
mode: workflow
name: qa获取
use_icon_as_answer_icon: false
kind: app
version: 0.1.2
workflow:
conversation_variables: []
environment_variables: []
features:
file_upload:
allowed_file_extensions:
- .JPG
- .JPEG
- .PNG
- .GIF
- .WEBP
- .SVG
allowed_file_types:
- image
allowed_file_upload_methods:
- local_file
- remote_url
enabled: false
fileUploadConfig:
audio_file_size_limit: 50
batch_count_limit: 5
file_size_limit: 15
image_file_size_limit: 10
video_file_size_limit: 100
image:
enabled: false
number_limits: 3
transfer_methods:
- local_file
- remote_url
number_limits: 3
opening_statement: ''
retriever_resource:
enabled: true
sensitive_word_avoidance:
enabled: false
speech_to_text:
enabled: false
suggested_questions: []
suggested_questions_after_answer:
enabled: false
text_to_speech:
enabled: false
language: ''
voice: ''
graph:
edges:
- data:
isInIteration: false
sourceType: start
targetType: http-request
id: 1742432991068-source-1742441313376-target
source: '1742432991068'
sourceHandle: source
target: '1742441313376'
targetHandle: target
type: custom
zIndex: 0
- data:
isInIteration: false
sourceType: http-request
targetType: tool
id: 1742441313376-source-1742441430248-target
source: '1742441313376'
sourceHandle: source
target: '1742441430248'
targetHandle: target
type: custom
zIndex: 0
- data:
isInIteration: false
sourceType: tool
targetType: code
id: 1742441430248-source-1742441888151-target
source: '1742441430248'
sourceHandle: source
target: '1742441888151'
targetHandle: target
type: custom
zIndex: 0
- data:
isInIteration: false
sourceType: code
targetType: iteration
id: 1742441888151-source-1742442381550-target
source: '1742441888151'
sourceHandle: source
target: '1742442381550'
targetHandle: target
type: custom
zIndex: 0
- data:
isInIteration: false
sourceType: iteration
targetType: end
id: 1742442381550-source-1742441453118-target
source: '1742442381550'
sourceHandle: source
target: '1742441453118'
targetHandle: target
type: custom
zIndex: 0
- data:
isInIteration: true
iteration_id: '1742442381550'
sourceType: iteration-start
targetType: llm
id: 1742442381550start-source-1742442494479-target
source: 1742442381550start
sourceHandle: source
target: '1742442494479'
targetHandle: target
type: custom
zIndex: 1002
- data:
isInIteration: true
iteration_id: '1742442381550'
sourceType: llm
targetType: http-request
id: 1742442494479-source-1742453246589-target
source: '1742442494479'
sourceHandle: source
target: '1742453246589'
targetHandle: target
type: custom
zIndex: 1002
nodes:
- data:
desc: ''
selected: false
title: 开始
type: start
variables:
- label: url
max_length: 256
options: []
required: true
type: text-input
variable: url
height: 90
id: '1742432991068'
position:
x: 30
y: 274
positionAbsolute:
x: 30
y: 274
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
authorization:
config: null
type: no-auth
body:
data: []
type: none
desc: ''
headers: ''
method: get
params: url:{{#1742432991068.url#}}
selected: false
timeout:
max_connect_timeout: 0
max_read_timeout: 0
max_write_timeout: 0
title: HTTP 请求
type: http-request
url: http://192.168.100.199:8080/test/spiltText
variables: []
height: 110
id: '1742441313376'
position:
x: 334
y: 274
positionAbsolute:
x: 334
y: 274
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
desc: ''
provider_id: json_process
provider_name: json_process
provider_type: builtin
selected: false
title: JSON 解析
tool_configurations:
ensure_ascii: 0
tool_label: JSON 解析
tool_name: parse
tool_parameters:
content:
type: mixed
value: '{{#1742441313376.body#}}'
json_filter:
type: mixed
value: data
type: tool
height: 90
id: '1742441430248'
position:
x: 638
y: 274
positionAbsolute:
x: 638
y: 274
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
desc: ''
outputs:
- value_selector:
- '1742441888151'
- result
variable: text
selected: false
title: 结束
type: end
height: 90
id: '1742441453118'
position:
x: 1858.5714285714287
y: 618.2857142857143
positionAbsolute:
x: 1858.5714285714287
y: 618.2857142857143
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
code: "import json\n\ndef main(input_str: str) -> dict:\n # 直接将JSON格式字符串转换为Python列表\n\
\ result_array = json.loads(input_str)\n\n return {\n \"result\"\
: result_array,\n }"
code_language: python3
desc: ''
outputs:
result:
children: null
type: array[string]
selected: false
title: 代码执行
type: code
variables:
- value_selector:
- '1742441430248'
- text
variable: input_str
height: 54
id: '1742441888151'
position:
x: 942
y: 274
positionAbsolute:
x: 942
y: 274
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
- data:
desc: ''
height: 441
iterator_selector:
- '1742441888151'
- result
output_selector:
- '1742442494479'
- text
output_type: array[string]
selected: false
start_node_id: 1742442381550start
title: 迭代
type: iteration
width: 717
height: 441
id: '1742442381550'
position:
x: 1035.9999999999998
y: 516.8571428571428
positionAbsolute:
x: 1035.9999999999998
y: 516.8571428571428
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 717
zIndex: 1
- data:
desc: ''
isInIteration: true
selected: false
title: ''
type: iteration-start
draggable: false
height: 48
id: 1742442381550start
parentId: '1742442381550'
position:
x: 24
y: 68
positionAbsolute:
x: 1059.9999999999998
y: 584.8571428571428
selectable: false
sourcePosition: right
targetPosition: left
type: custom-iteration-start
width: 44
zIndex: 1002
- data:
context:
enabled: true
variable_selector:
- '1742442381550'
- item
desc: ''
isInIteration: true
iteration_id: '1742442381550'
model:
completion_params:
num_predict: 9998
response_format: JSON
temperature: 0.7
mode: chat
name: qwen2.5:14b
provider: ollama
prompt_template:
- id: e2fd2d22-4c4e-4929-803c-045830e1fa58
role: system
text: 有一段文本,按照句子分割,你就是作者需要你对着每个句子结合上下文提出问题。然后再以request 和answer输出answer是句子原本内容,request是你提出的问题
最多提出 30 个问题
- id: 92f72d68-feab-4efb-a184-acb5f80af9c8
role: user
text: '{{#1742442381550.item#}}是这段文本,以 JSON 的形式输出,输出的 JSON 需遵守以下的格式
{
"request": "xxxx",
"answer": "xxxx"
},
{
"request": "xxxx",
"answer": "xxxx"
}
]
除了json你不需要输出其他任何内容'
selected: true
title: LLM
type: llm
variables: []
vision:
enabled: false
height: 98
id: '1742442494479'
parentId: '1742442381550'
position:
x: 128
y: 68
positionAbsolute:
x: 1163.9999999999998
y: 584.8571428571428
selected: true
sourcePosition: right
targetPosition: left
type: custom
width: 244
zIndex: 1002
- data:
authorization:
config: null
type: no-auth
body:
data:
- id: key-value-33
key: ''
type: text
value: '{{#1742442494479.text#}}'
type: json
desc: ''
headers: ''
isInIteration: true
iteration_id: '1742442381550'
method: post
params: ''
selected: false
timeout:
max_connect_timeout: 0
max_read_timeout: 0
max_write_timeout: 0
title: HTTP 请求 2
type: http-request
url: http://192.168.100.199:8080/qa/qa/setQa
variables: []
height: 110
id: '1742453246589'
parentId: '1742442381550'
position:
x: 434.8571428571429
y: 65.14285714285722
positionAbsolute:
x: 1470.8571428571427
y: 582
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
zIndex: 1002
viewport:
x: 98.85000000000014
y: -5.149999999999864
zoom: 0.7
http接口的java文件
这个文件可以将文章安装以句子为最小单位不超过textNum的字数切割。这样子就不会将句子切割开产生歧义。
public static String[] readFilesV2(String filePath, boolean delete, Integer textNum) {
ArrayList<String> stringList = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(Files.newInputStream(Paths.get(filePath)), StandardCharsets.UTF_8))) {
StringBuilder currentFragment = new StringBuilder();
StringBuilder currentSentence = new StringBuilder();
int charInt;
while ((charInt = reader.read()) != -1) {
char c = (char) charInt;
currentSentence.append(c);
// 检测到句子结束(以句号结尾)
if (c == '。') {
String sentence = currentSentence.toString();
currentSentence.setLength(0); // 重置当前句子
// 判断是否超过字符限制
if (currentFragment.length() + sentence.length() > textNum) {
if (currentFragment.length() > 0) {
stringList.add(currentFragment.toString());
currentFragment.setLength(0);
}
// 处理超长句子(单独成段)
if (sentence.length() > textNum) {
stringList.add(sentence);
} else {
currentFragment.append(sentence);
}
} else {
currentFragment.append(sentence);
}
}
}
// 处理最后一个未满的片段
if (currentFragment.length() > 0) {
stringList.add(currentFragment.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
// 打印结果验证
for (String s : stringList) {
System.out.println(s + " | Length: " + s.length());
}
// 删除文件逻辑
if (delete) {
try {
Files.delete(Paths.get(filePath));
System.out.println("文件已成功删除。");
} catch (IOException e) {
System.err.println("无法删除文件: " + e.getMessage());
}
}
return stringList.toArray(new String[0]);
}
最后的接口就是将qa数据集持久化
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody QaBo bo) {
return toAjax(qaService.insertByBo(bo));
}
例子
注入数据
说实话,小学最困扰我的问题就是考清华还是考北大。我认真想过这个问题,清华这两个字有木有水,很绿色很治愈。而北大这两个字横横竖竖的像个刺头,太有棱角了。我不喜欢我初中的时候都不知道大学是要考的,我一直以为就和初中一样,是有一个离家近的保底大学念,所以我当时已经做好了念苏大的准备。直到中考出分之前,我才知道这个世界上还有职高的存在。直到我要念职高的那一刻,我真的崩溃了。我幻想的职高就是热血高校那样校霸在班里都得打老师的那种。像我这种小学就被打的人我爸不会让我给他洗内裤吧,我不会拿香烟在我的屁股上烫金字喜玩吧。那我可是我和我妈一起选完学校回家,我妈还庆幸我有学校念。我回到房间抱着我的书包开始哭,我真的以为这是我书包最后一次里面还能放教科书了。念了职高不得给校霸走私。德平呢,后来发现其实还好,大家只是成绩差一点,人还是挺好的。我高一高二成绩都很差,垫底的那种。直到高三的时候,我们拿到一份单子,里面有我们能考的院校名单。我一看不仅没有苏大,也没有清华。我们我我的最好的本科也是一个我从来没有听过的学校。我当时一下子醒悟了,我们能考的最好的院校可能都是普高生们都不会填报志愿的学校。我觉得我的智商没有问题,我不比他们差。于是我用了一个学期的时间重新学,回到宿舍拿台灯背书,最后还是以班级前十的成绩考到了这个学校。再加上二三年北大的身份,我应该是这个职高的荣誉校友才对。后来大学毕业,小时候看那种文章,说什么村里总算出来一个本科生,负责任的说,我觉得我的未来一片光明,我带着本科的毕业证和管理学的学士学位证回到家乡,我大学学的什么进出口贸易战术。4PS sat分析可口可乐公司的营销策略。我当时觉得对于一个三线的县级市来说,这两张证书应该能搞点金融诈骗。没想到通过关系也只能去卖汽车保险,去卖那些正常人根本不需要的汽车延保,也确实是金融诈骗,我负责诈骗,他负责金融。然后我干了三个月,因为没有开单被开掉了,后来又陆陆续续的干了很多销售工作都不行,然后我就在家呆着,那段时间在家浑浑噩噩的,我爸就来开导我,他说好男儿志在四方,儿子爸爸爱你,你想做什么爸爸都支持你我说我想创业,我爸说不行。我说我想当博主,我爸说也不行。我爸说他对我的爱只够支持我当一名光荣的化工厂化验员。我不喜欢化工这两个字,横横竖竖的像个北大,而我不喜欢北大。我说daddy,dad, dad, daddy. 你儿子气质出众能做idol,声音软糯能当歌手,风趣幽默能去陪酒,臀型软糯能盆多肉。反正绝对不可能去给资本当狗。我父母那段时间真的很着急我的工作问题,他们对我最大的要求就是有个班上就行,反正就开车带我去附近工厂介绍这个是外资的,这个是合资的,这个待遇好好这个班扳倒掏心窝子的说我不服气,我不服气,脱掉长衫,这长衫是我被哄着骗着打着袜子才穿上的这长衫是我花了16年穿上的这长衫是我们我穿上的这长衫我本可以不穿的长衫遮住了我的无能,能遮住了我的屁股上那盘没有下完的景字旗。但是我看着爸爸上的的皱纹,以前念书的时候总说燕雀安知鸿鹄之志。业后几年的努努力就用力憋憋了个屁屁。不不不不不不不不不,反正我出来我做的饭饭后谈资的资格都没有我看着面前夜不停息的大厂,也许有些人注定就是厌缺吧。我对我爸说,行,我去面试。第二天我带着简历就去那个化工厂面试,这是正经大厂。面试一共有两轮,第一轮我和另一个面试的人安排在一个房间里填表,填一些基本信息。我印象最深的一道题是问我英语字母表一共有多少个字母,并且让我默写下来。我真的我第一眼看到这个问题的时候,我还以为是道陷阱题,这不会是通过答案要对我进行心理测写吧。后来发现是我多虑了,因为和我一起面试的哥们还等着抄我答案呢。然后我顺利的去了第二轮面试,HR拿着我的简历看,然后这个HR对我说了改变我一生的话。他说你本科毕业从事过自媒体行业,你的特长爱好都表明你是一个思维很活跃的人,而且你不是化工专业的,所以你这辈子都不会有晋升的机会。但你面试的这个岗位,我说直接一点,我现在到马路上随便拉一个人,他只要能呼吸,他就能干,如果你想好了,想要这份工作,我现在就可以录用你,但是我觉得你的生活不该如此。听完这番话,我拿着简历站在厂子门口思考我的人生。我不知道我该干什么,我不知道该怎么跟我爸妈说,但我知道这样的生活不是我想过的。后来的一个月就有了叫我sky就好这个账号。朋友回顾这前面二十多年,有太多我都以为天塌了的瞬间。学生时期的每一次考试,失恋后觉得没了他就活不下去的时候,恋爱时候借的2万元网贷进入社会那些理想和现实有落差的时候,投出上百份简历辗转的那些夜晚,每一个我觉得天大的走不出来的事情,现在都成了我笔下拿来吐槽的素材。可能你现在也面临很多困扰你的问题,可能你也为了生活妥协的做着自己不喜欢的工作,甚至你可能就是当年抄我答案进场的小哥。我知道崩溃的情绪是真的,但也请你相信,一定都会过去的。而那个司马迁笔下的傻鸟厌倦,现在已经是三级保护动物了。嘿朋友,人生的容错率比你想的高得多,而你所在的行业也比你想的短命,而你我的朋友,你的命还长着呢。
产生的数据集效果

总之效果还是很不错的