doomiaichat 4.7.0 → 4.9.0
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.
- package/dist/openai.d.ts +2 -2
- package/dist/openai.js +190 -121
- package/package.json +1 -1
- package/src/openai.ts +86 -24
package/dist/openai.d.ts
CHANGED
|
@@ -82,7 +82,7 @@ export default class OpenAIGpt extends GptBase {
|
|
|
82
82
|
* @param {*} messages
|
|
83
83
|
* @returns
|
|
84
84
|
*/
|
|
85
|
-
protected pickUpFaqContent(messages: Array<any>): Array<FaqItem
|
|
85
|
+
protected pickUpFaqContent(messages: Array<any>): Promise<Array<FaqItem>>;
|
|
86
86
|
/**
|
|
87
87
|
* 从指定的文本内容中生成一张试卷
|
|
88
88
|
* @param {*} content
|
|
@@ -97,7 +97,7 @@ export default class OpenAIGpt extends GptBase {
|
|
|
97
97
|
* @param {*} result
|
|
98
98
|
*
|
|
99
99
|
*/
|
|
100
|
-
protected pickUpQuestions(result: Array<any>, count: number, questiontype: string, score?: number): Array<QuestionItem
|
|
100
|
+
protected pickUpQuestions(result: Array<any>, count: number, questiontype: string, score?: number): Promise<Array<QuestionItem>>;
|
|
101
101
|
/**
|
|
102
102
|
* 验证JSON字符串是否是真正可转换为JSON的合法格式
|
|
103
103
|
* 这里只能做一个最简单的处理,就是用两端的符号
|
package/dist/openai.js
CHANGED
|
@@ -64,10 +64,19 @@ const QUESTION_ROLE_DEFINE = {
|
|
|
64
64
|
* 问题生成的Prompt
|
|
65
65
|
*/
|
|
66
66
|
const QUESTION_PROMPT = {
|
|
67
|
-
singlechoice: '根据以下内容,生成@ITEMCOUNT@道单选题,每道题目4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题一个正确答案,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]
|
|
68
|
-
multiplechoice: '根据以下内容,请生成@ITEMCOUNT@道多选题,提供4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题的答案至少有两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]
|
|
69
|
-
trueorfalse: '根据以下内容,请生成@ITEMCOUNT@道判断题,每道题正确和错误两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]
|
|
70
|
-
completion: '根据以下内容,请生成@ITEMCOUNT@道填空题和对应答案,输出结果必须是JSON数组并按照[{"question":"","answer":["填空答案1","填空答案2"]}]
|
|
67
|
+
singlechoice: '根据以下内容,生成@ITEMCOUNT@道单选题,每道题目4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题一个正确答案,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。',
|
|
68
|
+
multiplechoice: '根据以下内容,请生成@ITEMCOUNT@道多选题,提供4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题的答案至少有两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。',
|
|
69
|
+
trueorfalse: '根据以下内容,请生成@ITEMCOUNT@道判断题,每道题正确和错误两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。',
|
|
70
|
+
completion: '根据以下内容,请生成@ITEMCOUNT@道填空题和对应答案,输出结果必须是JSON数组并按照[{"question":"","answer":["填空答案1","填空答案2"]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。'
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* 问题生成的Prompt
|
|
74
|
+
*/
|
|
75
|
+
const QUESTION_PROMPT_FIXED = {
|
|
76
|
+
singlechoice: '[{"question":"","choice":[],"answer":[]}]',
|
|
77
|
+
multiplechoice: '[{"question":"","choice":[],"answer":[]}]',
|
|
78
|
+
trueorfalse: '[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]',
|
|
79
|
+
completion: '[{"question":"","answer":["填空答案1","填空答案2"]}]'
|
|
71
80
|
};
|
|
72
81
|
const QUESTION_TYPE = ['singlechoice', 'multiplechoice', 'trueorfalse', 'completion'];
|
|
73
82
|
class OpenAIGpt extends gptbase_1.default {
|
|
@@ -137,6 +146,12 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
137
146
|
max_tokens: Number((callChatOption === null || callChatOption === void 0 ? void 0 : callChatOption.maxtoken) || this.maxtoken),
|
|
138
147
|
n: Number((callChatOption === null || callChatOption === void 0 ? void 0 : callChatOption.replyCounts) || 1) || 1
|
|
139
148
|
}, axiosOption);
|
|
149
|
+
// console.log('finish_reason==>', response.data.choices)
|
|
150
|
+
////输出的内容不合规
|
|
151
|
+
if (response.data.choices[0].finish_reason === 'content_filter') {
|
|
152
|
+
console.log('content_filter');
|
|
153
|
+
return { successed: false, error: 'content_filter' };
|
|
154
|
+
}
|
|
140
155
|
return { successed: true, message: response.data.choices, usage: response.data.usage };
|
|
141
156
|
}
|
|
142
157
|
catch (error) {
|
|
@@ -372,8 +387,15 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
372
387
|
* @returns
|
|
373
388
|
*/ //并在答案末尾处必须给出答案内容中的关键词
|
|
374
389
|
generateQuestionsFromContent(content, count = 1, everyContentLength = SECTION_LENGTH, axiosOption = {}) {
|
|
390
|
+
var _a;
|
|
375
391
|
return __awaiter(this, void 0, void 0, function* () {
|
|
376
392
|
let arrContent = this.splitLongText(content, everyContentLength || SECTION_LENGTH);
|
|
393
|
+
///如果最后一段的文字内容过短,则把最后一段内容追加到前一段中,并删除最后一段
|
|
394
|
+
let totalLen = arrContent.length;
|
|
395
|
+
if (totalLen >= 2 && (((_a = arrContent[totalLen - 1]) === null || _a === void 0 ? void 0 : _a.length) || 0) < 100) {
|
|
396
|
+
arrContent[totalLen - 2] += arrContent[totalLen - 1];
|
|
397
|
+
arrContent.splice(totalLen - 1, 1);
|
|
398
|
+
}
|
|
377
399
|
///没20句话分为一组,适应大文件内容多次请求组合结果
|
|
378
400
|
///每一句话需要产生的题目
|
|
379
401
|
let questions4EverySentense = count / arrContent.length; //Math.ceil(arrContent.length / 20);
|
|
@@ -384,19 +406,18 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
384
406
|
let itemCount = Math.min(Math.ceil(questions4EverySentense), count - gotted);
|
|
385
407
|
let subarray = [
|
|
386
408
|
{ role: 'system', content: FAQ_ROLE_DEFINE },
|
|
387
|
-
{ role: 'user', content: `从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON
|
|
388
|
-
{ role: 'user', content: arrContent.slice(0, 1)[0]
|
|
389
|
-
}
|
|
409
|
+
{ role: 'user', content: `从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON数组结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组 []。` },
|
|
410
|
+
{ role: 'user', content: arrContent.slice(0, 1)[0] }
|
|
390
411
|
];
|
|
391
|
-
|
|
392
|
-
//subarray.unshift({ role: 'system', content: `你是一位专业培训师,从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON数组结构输出。` })
|
|
393
|
-
// subarray.unshift({role: 'system', content: FAQ_ROLE_DEFINE});
|
|
394
|
-
//subarray.unshift({ role: 'system', content: `你是一位专业程序开发工程师,根据以下内容,按照[{"question":"问题内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]JSON数组结构,给出${itemCount}条提问问题及答案以及答案关键词` })
|
|
395
|
-
// console.log('subarray', subarray)
|
|
412
|
+
console.log('Faq Question Pick Prompt:', subarray);
|
|
396
413
|
let result = yield this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
414
|
+
///如果请求发生了网络错误(不是内容合规问题),则再重试一次,如果任然有错则放弃
|
|
415
|
+
if (!result.successed && result.error != 'content_filter') {
|
|
416
|
+
console.log('network error,retry onemore time');
|
|
417
|
+
result = yield this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
418
|
+
}
|
|
397
419
|
if (result.successed && result.message) {
|
|
398
|
-
|
|
399
|
-
let msgs = this.pickUpFaqContent(result.message);
|
|
420
|
+
let msgs = yield this.pickUpFaqContent(result.message);
|
|
400
421
|
if (msgs.length) {
|
|
401
422
|
///对外发送检出问答题的信号
|
|
402
423
|
this.emit('parseout', { type: 'qa', items: msgs });
|
|
@@ -420,32 +441,49 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
420
441
|
*/
|
|
421
442
|
pickUpFaqContent(messages) {
|
|
422
443
|
var _a, _b;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
444
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
445
|
+
if (!((_b = (_a = messages[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content))
|
|
446
|
+
return [];
|
|
447
|
+
let answerString = messages[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
448
|
+
if (answerString === '[]')
|
|
449
|
+
return [];
|
|
450
|
+
let jsonObj = this.fixedJsonString(answerString);
|
|
451
|
+
if (!jsonObj.length) {
|
|
452
|
+
let fixedAsk = [
|
|
453
|
+
{ role: 'system', content: '角色扮演:假设你是一位高级JSON数据分析师' },
|
|
454
|
+
{ role: 'user', content: `请分析以下内容,严格按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的标准JSON数组结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组,无需提供参考。` },
|
|
455
|
+
{ role: 'user', content: answerString },
|
|
456
|
+
];
|
|
457
|
+
console.log('pickUpFaqContent fixedAsk', fixedAsk);
|
|
458
|
+
let fixedJsonResult = yield this.chatRequest(fixedAsk, { replyCounts: 1 }, {});
|
|
459
|
+
if (fixedJsonResult.successed) {
|
|
460
|
+
answerString = fixedJsonResult.message[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
461
|
+
jsonObj = this.fixedJsonString(answerString);
|
|
439
462
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
463
|
+
if (!jsonObj.length)
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
//let jsonObj = JSON.parse(answerString);
|
|
468
|
+
//let jsonObj = eval(answerString);
|
|
469
|
+
jsonObj.map((item) => {
|
|
470
|
+
let realKeyword = [];
|
|
471
|
+
let keywords = (item.keywords + '').split(',');
|
|
472
|
+
let answer = item.answer || '';
|
|
473
|
+
for (const k of keywords) {
|
|
474
|
+
if (k && answer.indexOf(k) >= 0)
|
|
475
|
+
realKeyword.push(k);
|
|
476
|
+
}
|
|
477
|
+
item.keywords = realKeyword;
|
|
478
|
+
return item;
|
|
479
|
+
});
|
|
480
|
+
return jsonObj;
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
console.log('JSON error', err);
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
});
|
|
449
487
|
}
|
|
450
488
|
/**
|
|
451
489
|
* 从指定的文本内容中生成一张试卷
|
|
@@ -457,28 +495,34 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
457
495
|
* @returns
|
|
458
496
|
*/ //并在答案末尾处必须给出答案内容中的关键词
|
|
459
497
|
generateExaminationPaperFromContent(content, paperOption = {}, everyContentLength = SECTION_LENGTH, axiosOption = {}) {
|
|
460
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
498
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
461
499
|
return __awaiter(this, void 0, void 0, function* () {
|
|
462
500
|
let arrContent = this.splitLongText(content, everyContentLength || SECTION_LENGTH);
|
|
501
|
+
///如果最后一段的文字内容过短,则把最后一段内容追加到前一段中,并删除最后一段
|
|
502
|
+
let totalLen = arrContent.length;
|
|
503
|
+
if (totalLen >= 2 && (((_a = arrContent[totalLen - 1]) === null || _a === void 0 ? void 0 : _a.length) || 0) < 100) {
|
|
504
|
+
arrContent[totalLen - 2] += arrContent[totalLen - 1];
|
|
505
|
+
arrContent.splice(totalLen - 1, 1);
|
|
506
|
+
}
|
|
463
507
|
let sectionCount = {
|
|
464
|
-
singlechoice: (((
|
|
465
|
-
multiplechoice: (((
|
|
466
|
-
trueorfalse: (((
|
|
467
|
-
completion: (((
|
|
508
|
+
singlechoice: (((_b = paperOption.singlechoice) === null || _b === void 0 ? void 0 : _b.count) || 0) / arrContent.length,
|
|
509
|
+
multiplechoice: (((_c = paperOption.multiplechoice) === null || _c === void 0 ? void 0 : _c.count) || 0) / arrContent.length,
|
|
510
|
+
trueorfalse: (((_d = paperOption.trueorfalse) === null || _d === void 0 ? void 0 : _d.count) || 0) / arrContent.length,
|
|
511
|
+
completion: (((_e = paperOption.completion) === null || _e === void 0 ? void 0 : _e.count) || 0) / arrContent.length
|
|
468
512
|
};
|
|
469
513
|
///剩余待生成的题目数量
|
|
470
514
|
let remainCount = {
|
|
471
|
-
singlechoice: ((
|
|
472
|
-
multiplechoice: ((
|
|
473
|
-
trueorfalse: ((
|
|
474
|
-
completion: ((
|
|
515
|
+
singlechoice: ((_f = paperOption.singlechoice) === null || _f === void 0 ? void 0 : _f.count) || 0,
|
|
516
|
+
multiplechoice: ((_g = paperOption.multiplechoice) === null || _g === void 0 ? void 0 : _g.count) || 0,
|
|
517
|
+
trueorfalse: ((_h = paperOption.trueorfalse) === null || _h === void 0 ? void 0 : _h.count) || 0,
|
|
518
|
+
completion: ((_j = paperOption.completion) === null || _j === void 0 ? void 0 : _j.count) || 0
|
|
475
519
|
};
|
|
476
520
|
///每种类型的题目的分数
|
|
477
521
|
let ITEM_SCORE = {
|
|
478
|
-
singlechoice: ((
|
|
479
|
-
multiplechoice: ((
|
|
480
|
-
trueorfalse: ((
|
|
481
|
-
completion: ((
|
|
522
|
+
singlechoice: ((_k = paperOption.singlechoice) === null || _k === void 0 ? void 0 : _k.score) || 0,
|
|
523
|
+
multiplechoice: ((_l = paperOption.multiplechoice) === null || _l === void 0 ? void 0 : _l.score) || 0,
|
|
524
|
+
trueorfalse: ((_m = paperOption.trueorfalse) === null || _m === void 0 ? void 0 : _m.score) || 0,
|
|
525
|
+
completion: ((_o = paperOption.completion) === null || _o === void 0 ? void 0 : _o.score) || 0
|
|
482
526
|
};
|
|
483
527
|
///最后生成出来的结果
|
|
484
528
|
let paperReturned = {
|
|
@@ -489,7 +533,6 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
489
533
|
/**
|
|
490
534
|
* 每种类型的题目进行遍历
|
|
491
535
|
*/
|
|
492
|
-
console.log('arrContent.length', arrContent.length);
|
|
493
536
|
noMoreQuestionRetrive = true;
|
|
494
537
|
for (const key of QUESTION_TYPE) {
|
|
495
538
|
///还需要抓取题目
|
|
@@ -505,10 +548,15 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
505
548
|
// subarray.unshift()
|
|
506
549
|
console.log('subarray', subarray);
|
|
507
550
|
let result = yield this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
551
|
+
///如果请求发生了网络错误(不是内容合规问题),则再重试一次,如果任然有错则放弃
|
|
552
|
+
if (!result.successed && result.error != 'content_filter') {
|
|
553
|
+
console.log('network error,retry onemore time');
|
|
554
|
+
result = yield this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
555
|
+
}
|
|
508
556
|
console.log('subarray returned', result.successed);
|
|
509
557
|
if (result.successed && result.message) {
|
|
510
558
|
//console.log('paper result', key, result.message.length)
|
|
511
|
-
let pickedQuestions = this.pickUpQuestions(result.message, itemCount, key, ITEM_SCORE[key]);
|
|
559
|
+
let pickedQuestions = yield this.pickUpQuestions(result.message, itemCount, key, ITEM_SCORE[key]);
|
|
512
560
|
if (pickedQuestions.length) {
|
|
513
561
|
///对外发送检出题目的信号
|
|
514
562
|
this.emit('parseout', { type: 'question', name: key, items: pickedQuestions });
|
|
@@ -537,76 +585,97 @@ class OpenAIGpt extends gptbase_1.default {
|
|
|
537
585
|
*/
|
|
538
586
|
pickUpQuestions(result, count, questiontype, score = 1) {
|
|
539
587
|
var _a, _b;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
588
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
589
|
+
if (!((_b = (_a = result[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content))
|
|
590
|
+
return [];
|
|
591
|
+
let answerString = result[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
592
|
+
if (answerString === '[]')
|
|
593
|
+
return [];
|
|
594
|
+
let jsonObj = this.fixedJsonString(answerString);
|
|
595
|
+
////修复的结果无法用程序修复,请求GPT来分析修复一下这个结果
|
|
596
|
+
if (!jsonObj.length) {
|
|
597
|
+
let fixedAsk = [
|
|
598
|
+
{ role: 'system', content: '角色扮演:假设你是一位高级JSON数据分析师' },
|
|
599
|
+
{ role: 'user', content: `请分析以下内容,严格按照${QUESTION_PROMPT_FIXED[questiontype]}的标准JSON数组结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组,无需提供参考。` },
|
|
600
|
+
{ role: 'user', content: answerString },
|
|
601
|
+
];
|
|
602
|
+
console.log('fixedAsk', fixedAsk);
|
|
603
|
+
let fixedJsonResult = yield this.chatRequest(fixedAsk, { replyCounts: 1 }, {});
|
|
604
|
+
if (fixedJsonResult.successed) {
|
|
605
|
+
answerString = fixedJsonResult.message[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
606
|
+
jsonObj = this.fixedJsonString(answerString);
|
|
607
|
+
}
|
|
608
|
+
if (!jsonObj.length)
|
|
609
|
+
return [];
|
|
610
|
+
}
|
|
611
|
+
let returnItems = [];
|
|
612
|
+
try {
|
|
613
|
+
// let jsonObj = JSON.parse(answerString);
|
|
614
|
+
returnItems = jsonObj.map((questionitem) => {
|
|
615
|
+
console.log('answer item from jsonObj', questionitem);
|
|
616
|
+
if (questionitem.choice && Array.isArray(questionitem.choice) && questiontype != 'completion') {
|
|
617
|
+
questionitem.fullanswer = (questionitem.answer + '').replace(/,|[^ABCDE]/g, '');
|
|
618
|
+
questionitem.score = score;
|
|
619
|
+
if (questionitem.choice) {
|
|
620
|
+
questionitem.choice = questionitem.choice.map((item, index) => {
|
|
621
|
+
let seqNo = 'ABCDEFG'[index]; //String.fromCharCode(65 + index);
|
|
622
|
+
let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig');
|
|
623
|
+
// console.log('itemitemitem', item)
|
|
624
|
+
//let answer = jsonObj.fullanswer
|
|
625
|
+
return {
|
|
626
|
+
id: seqNo,
|
|
627
|
+
content: (item + '').replace(correctReg, '').trim(),
|
|
628
|
+
iscorrect: (questionitem.fullanswer || '').indexOf(seqNo) >= 0 ? 1 : 0
|
|
629
|
+
//|| jsonObj.fullanswer.indexOf(m))
|
|
630
|
+
};
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
///如果是非判断题,题目的选项数量小于2 ,则无效
|
|
634
|
+
///如果是判断题,题目的选项必须=2
|
|
635
|
+
if (!questionitem.choice || (questiontype != 'trueorfalse' && questionitem.choice.length < 3) || (questiontype == 'trueorfalse' && questionitem.choice.length != 2)) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
567
638
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
639
|
+
switch (questiontype) {
|
|
640
|
+
case 'singlechoice':
|
|
641
|
+
questionitem.answer = (questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
|
|
642
|
+
break;
|
|
643
|
+
case 'multiplechoice':
|
|
644
|
+
questionitem.answer = Array.from(new Set((questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('')));
|
|
645
|
+
break;
|
|
646
|
+
case 'trueorfalse':
|
|
647
|
+
let rightItem = questionitem.choice.find((x) => { return x.iscorrect == 1; });
|
|
648
|
+
questionitem.answer = [(rightItem === null || rightItem === void 0 ? void 0 : rightItem.id) || 'Z']; //[(questionitem.answer + '').indexOf('正确') >= 0 ? 'A' : 'B']
|
|
649
|
+
break;
|
|
572
650
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
///正确选项和答案不一致
|
|
593
|
-
if (rightAnswer[0].id.toUpperCase() != (questionitem.answer[0] || '').toUpperCase())
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
///多选题验证
|
|
597
|
-
if (questiontype == 'multiplechoice') {
|
|
598
|
-
let rightAnswer = questionitem.choice ? questionitem.choice.filter((item) => { return item.iscorrect === 1; }) : [];
|
|
599
|
-
///单选题的正确选项大于了1个
|
|
600
|
-
if (rightAnswer.length === 0 || !questionitem.answer || questionitem.answer.length === 0)
|
|
651
|
+
///单选题验证
|
|
652
|
+
if (questiontype == 'singlechoice') {
|
|
653
|
+
let rightAnswer = questionitem.choice ? questionitem.choice.filter((item) => { return item.iscorrect === 1; }) : [];
|
|
654
|
+
///单选题的正确选项大于了1个
|
|
655
|
+
if (rightAnswer.length != 1 || !questionitem.answer || questionitem.answer.length !== 1)
|
|
656
|
+
return null;
|
|
657
|
+
///正确选项和答案不一致
|
|
658
|
+
if (rightAnswer[0].id.toUpperCase() != (questionitem.answer[0] || '').toUpperCase())
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
///多选题验证
|
|
662
|
+
if (questiontype == 'multiplechoice') {
|
|
663
|
+
let rightAnswer = questionitem.choice ? questionitem.choice.filter((item) => { return item.iscorrect === 1; }) : [];
|
|
664
|
+
///单选题的正确选项大于了1个
|
|
665
|
+
if (rightAnswer.length === 0 || !questionitem.answer || questionitem.answer.length === 0)
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
///判断题验证:防止没有答案的
|
|
669
|
+
if (questiontype == 'trueorfalse' && !questionitem.answer.length)
|
|
601
670
|
return null;
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
671
|
+
return questionitem;
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
catch (err) {
|
|
675
|
+
console.log('error happened:', err);
|
|
676
|
+
}
|
|
677
|
+
return returnItems.filter(i => { return i != null; }).slice(0, count);
|
|
678
|
+
});
|
|
610
679
|
}
|
|
611
680
|
/**
|
|
612
681
|
* 验证JSON字符串是否是真正可转换为JSON的合法格式
|
package/package.json
CHANGED
package/src/openai.ts
CHANGED
|
@@ -52,10 +52,20 @@ const QUESTION_ROLE_DEFINE: any = {
|
|
|
52
52
|
* 问题生成的Prompt
|
|
53
53
|
*/
|
|
54
54
|
const QUESTION_PROMPT: any ={
|
|
55
|
-
singlechoice:'根据以下内容,生成@ITEMCOUNT@道单选题,每道题目4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题一个正确答案,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]
|
|
56
|
-
multiplechoice: '根据以下内容,请生成@ITEMCOUNT@道多选题,提供4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题的答案至少有两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]
|
|
57
|
-
trueorfalse: '根据以下内容,请生成@ITEMCOUNT@道判断题,每道题正确和错误两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]
|
|
58
|
-
completion: '根据以下内容,请生成@ITEMCOUNT@道填空题和对应答案,输出结果必须是JSON数组并按照[{"question":"","answer":["填空答案1","填空答案2"]}]
|
|
55
|
+
singlechoice:'根据以下内容,生成@ITEMCOUNT@道单选题,每道题目4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题一个正确答案,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。',
|
|
56
|
+
multiplechoice: '根据以下内容,请生成@ITEMCOUNT@道多选题,提供4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题的答案至少有两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。',
|
|
57
|
+
trueorfalse: '根据以下内容,请生成@ITEMCOUNT@道判断题,每道题正确和错误两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。',
|
|
58
|
+
completion: '根据以下内容,请生成@ITEMCOUNT@道填空题和对应答案,输出结果必须是JSON数组并按照[{"question":"","answer":["填空答案1","填空答案2"]}]的结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组。'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 问题生成的Prompt
|
|
63
|
+
*/
|
|
64
|
+
const QUESTION_PROMPT_FIXED: any = {
|
|
65
|
+
singlechoice: '[{"question":"","choice":[],"answer":[]}]',
|
|
66
|
+
multiplechoice: '[{"question":"","choice":[],"answer":[]}]',
|
|
67
|
+
trueorfalse: '[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]',
|
|
68
|
+
completion: '[{"question":"","answer":["填空答案1","填空答案2"]}]'
|
|
59
69
|
}
|
|
60
70
|
const QUESTION_TYPE: string[] = ['singlechoice', 'multiplechoice', 'trueorfalse', 'completion']
|
|
61
71
|
|
|
@@ -129,6 +139,12 @@ export default class OpenAIGpt extends GptBase {
|
|
|
129
139
|
max_tokens: Number(callChatOption?.maxtoken || this.maxtoken),
|
|
130
140
|
n: Number(callChatOption?.replyCounts || 1) || 1
|
|
131
141
|
}, axiosOption);
|
|
142
|
+
// console.log('finish_reason==>', response.data.choices)
|
|
143
|
+
////输出的内容不合规
|
|
144
|
+
if (response.data.choices[0].finish_reason ==='content_filter') {
|
|
145
|
+
console.log('content_filter')
|
|
146
|
+
return { successed: false, error:'content_filter'}
|
|
147
|
+
}
|
|
132
148
|
return { successed: true, message: response.data.choices, usage: response.data.usage };
|
|
133
149
|
} catch (error) {
|
|
134
150
|
console.log('result is error ', error)
|
|
@@ -342,6 +358,12 @@ export default class OpenAIGpt extends GptBase {
|
|
|
342
358
|
*///并在答案末尾处必须给出答案内容中的关键词
|
|
343
359
|
override async generateQuestionsFromContent(content: string, count: number = 1, everyContentLength: number = SECTION_LENGTH, axiosOption: any = {}): Promise<ChatReponse> {
|
|
344
360
|
let arrContent = this.splitLongText(content, everyContentLength || SECTION_LENGTH);
|
|
361
|
+
///如果最后一段的文字内容过短,则把最后一段内容追加到前一段中,并删除最后一段
|
|
362
|
+
let totalLen = arrContent.length;
|
|
363
|
+
if (totalLen>=2 && (arrContent[totalLen-1]?.length||0)<100){
|
|
364
|
+
arrContent[totalLen - 2] += arrContent[totalLen - 1];
|
|
365
|
+
arrContent.splice(totalLen-1,1);
|
|
366
|
+
}
|
|
345
367
|
///没20句话分为一组,适应大文件内容多次请求组合结果
|
|
346
368
|
///每一句话需要产生的题目
|
|
347
369
|
let questions4EverySentense: number = count / arrContent.length; //Math.ceil(arrContent.length / 20);
|
|
@@ -352,28 +374,25 @@ export default class OpenAIGpt extends GptBase {
|
|
|
352
374
|
let itemCount = Math.min(Math.ceil(questions4EverySentense), count - gotted);
|
|
353
375
|
let subarray = [
|
|
354
376
|
{ role: 'system', content: FAQ_ROLE_DEFINE },
|
|
355
|
-
{ role: 'user', content: `从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON
|
|
356
|
-
{ role: 'user', content: arrContent.slice(0, 1)[0]
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
//subarray.push({ role: 'user', content:'请根据上述内容,给出一道提问与答案以及答案关键词,按照先问题内容,再标准答案,再关键词的顺序输出,关键词之间用、分开'})
|
|
360
|
-
//subarray.unshift({ role: 'system', content: `你是一位专业培训师,从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON数组结构输出。` })
|
|
361
|
-
// subarray.unshift({role: 'system', content: FAQ_ROLE_DEFINE});
|
|
362
|
-
//subarray.unshift({ role: 'system', content: `你是一位专业程序开发工程师,根据以下内容,按照[{"question":"问题内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]JSON数组结构,给出${itemCount}条提问问题及答案以及答案关键词` })
|
|
363
|
-
// console.log('subarray', subarray)
|
|
377
|
+
{ role: 'user', content: `从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON数组结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组 []。`},
|
|
378
|
+
{ role: 'user', content: arrContent.slice(0, 1)[0]}
|
|
379
|
+
]
|
|
380
|
+
console.log('Faq Question Pick Prompt:', subarray)
|
|
364
381
|
let result = await this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
382
|
+
///如果请求发生了网络错误(不是内容合规问题),则再重试一次,如果任然有错则放弃
|
|
383
|
+
if (!result.successed && result.error!='content_filter'){
|
|
384
|
+
console.log('network error,retry onemore time')
|
|
385
|
+
result = await this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
386
|
+
}
|
|
365
387
|
if (result.successed && result.message) {
|
|
366
|
-
|
|
367
|
-
let msgs = this.pickUpFaqContent(result.message);
|
|
388
|
+
let msgs = await this.pickUpFaqContent(result.message);
|
|
368
389
|
if (msgs.length) {
|
|
369
390
|
///对外发送检出问答题的信号
|
|
370
391
|
this.emit('parseout', { type: 'qa', items: msgs })
|
|
371
392
|
gotted += msgs.length; //result.message.length;
|
|
372
393
|
faqs = faqs.concat(msgs);
|
|
373
|
-
|
|
374
394
|
}
|
|
375
395
|
}
|
|
376
|
-
|
|
377
396
|
////删除已经处理的文本
|
|
378
397
|
arrContent.splice(0, 1);
|
|
379
398
|
}
|
|
@@ -388,11 +407,27 @@ export default class OpenAIGpt extends GptBase {
|
|
|
388
407
|
* @param {*} messages
|
|
389
408
|
* @returns
|
|
390
409
|
*/
|
|
391
|
-
protected pickUpFaqContent(messages: Array<any>): Array<FaqItem
|
|
410
|
+
protected async pickUpFaqContent(messages: Array<any>): Promise<Array<FaqItem>> {
|
|
392
411
|
if (!messages[0]?.message?.content) return [];
|
|
393
412
|
let answerString = messages[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
413
|
+
if (answerString==='[]') return [];
|
|
394
414
|
let jsonObj = this.fixedJsonString(answerString);
|
|
395
|
-
if (!jsonObj.length)
|
|
415
|
+
if (!jsonObj.length){
|
|
416
|
+
let fixedAsk = [
|
|
417
|
+
{ role: 'system', content: '角色扮演:假设你是一位高级JSON数据分析师' },
|
|
418
|
+
{ role: 'user', content: `请分析以下内容,严格按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的标准JSON数组结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组,无需提供参考。` },
|
|
419
|
+
{ role: 'user', content: answerString },
|
|
420
|
+
]
|
|
421
|
+
console.log('pickUpFaqContent fixedAsk', fixedAsk)
|
|
422
|
+
let fixedJsonResult: any = await this.chatRequest(fixedAsk, { replyCounts: 1 }, {})
|
|
423
|
+
if (fixedJsonResult.successed) {
|
|
424
|
+
answerString = fixedJsonResult.message[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
425
|
+
jsonObj = this.fixedJsonString(answerString);
|
|
426
|
+
}
|
|
427
|
+
if (!jsonObj.length) return []
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
396
431
|
try {
|
|
397
432
|
//let jsonObj = JSON.parse(answerString);
|
|
398
433
|
//let jsonObj = eval(answerString);
|
|
@@ -424,6 +459,12 @@ export default class OpenAIGpt extends GptBase {
|
|
|
424
459
|
*///并在答案末尾处必须给出答案内容中的关键词
|
|
425
460
|
override async generateExaminationPaperFromContent(content: string, paperOption: any = {}, everyContentLength: number = SECTION_LENGTH, axiosOption: any = {}): Promise<ExaminationPaperResult> {
|
|
426
461
|
let arrContent = this.splitLongText(content, everyContentLength || SECTION_LENGTH);
|
|
462
|
+
///如果最后一段的文字内容过短,则把最后一段内容追加到前一段中,并删除最后一段
|
|
463
|
+
let totalLen = arrContent.length;
|
|
464
|
+
if (totalLen >= 2 && (arrContent[totalLen - 1]?.length || 0) < 100) {
|
|
465
|
+
arrContent[totalLen - 2] += arrContent[totalLen - 1];
|
|
466
|
+
arrContent.splice(totalLen - 1, 1);
|
|
467
|
+
}
|
|
427
468
|
let sectionCount: any = {
|
|
428
469
|
singlechoice: (paperOption.singlechoice?.count || 0) / arrContent.length,
|
|
429
470
|
multiplechoice: (paperOption.multiplechoice?.count || 0) / arrContent.length,
|
|
@@ -455,7 +496,6 @@ export default class OpenAIGpt extends GptBase {
|
|
|
455
496
|
/**
|
|
456
497
|
* 每种类型的题目进行遍历
|
|
457
498
|
*/
|
|
458
|
-
console.log('arrContent.length', arrContent.length)
|
|
459
499
|
noMoreQuestionRetrive = true;
|
|
460
500
|
for (const key of QUESTION_TYPE) {
|
|
461
501
|
///还需要抓取题目
|
|
@@ -472,10 +512,15 @@ export default class OpenAIGpt extends GptBase {
|
|
|
472
512
|
// subarray.unshift()
|
|
473
513
|
console.log('subarray', subarray)
|
|
474
514
|
let result = await this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
515
|
+
///如果请求发生了网络错误(不是内容合规问题),则再重试一次,如果任然有错则放弃
|
|
516
|
+
if (!result.successed && result.error != 'content_filter') {
|
|
517
|
+
console.log('network error,retry onemore time')
|
|
518
|
+
result = await this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
|
|
519
|
+
}
|
|
475
520
|
console.log('subarray returned', result.successed)
|
|
476
521
|
if (result.successed && result.message) {
|
|
477
522
|
//console.log('paper result', key, result.message.length)
|
|
478
|
-
let pickedQuestions = this.pickUpQuestions(result.message, itemCount, key, ITEM_SCORE[key]);
|
|
523
|
+
let pickedQuestions = await this.pickUpQuestions(result.message, itemCount, key, ITEM_SCORE[key]);
|
|
479
524
|
if (pickedQuestions.length) {
|
|
480
525
|
///对外发送检出题目的信号
|
|
481
526
|
this.emit('parseout', { type: 'question', name: key, items: pickedQuestions })
|
|
@@ -502,11 +547,26 @@ export default class OpenAIGpt extends GptBase {
|
|
|
502
547
|
* @param {*} result
|
|
503
548
|
*
|
|
504
549
|
*/
|
|
505
|
-
protected pickUpQuestions(result: Array<any>, count: number, questiontype: string, score: number = 1): Array<QuestionItem
|
|
550
|
+
protected async pickUpQuestions(result: Array<any>, count: number, questiontype: string, score: number = 1): Promise<Array<QuestionItem>> {
|
|
506
551
|
if (!result[0]?.message?.content) return [];
|
|
507
552
|
let answerString = result[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
553
|
+
if (answerString === '[]') return [];
|
|
508
554
|
let jsonObj = this.fixedJsonString(answerString);
|
|
509
|
-
|
|
555
|
+
////修复的结果无法用程序修复,请求GPT来分析修复一下这个结果
|
|
556
|
+
if (!jsonObj.length){
|
|
557
|
+
let fixedAsk = [
|
|
558
|
+
{role:'system',content:'角色扮演:假设你是一位高级JSON数据分析师'},
|
|
559
|
+
{ role: 'user', content: `请分析以下内容,严格按照${QUESTION_PROMPT_FIXED[questiontype]}的标准JSON数组结构输出。如果内容不足以提取问题和答案,请直接输出JSON空数组,无需提供参考。` },
|
|
560
|
+
{ role: 'user', content: answerString },
|
|
561
|
+
]
|
|
562
|
+
console.log('fixedAsk', fixedAsk)
|
|
563
|
+
let fixedJsonResult:any =await this.chatRequest(fixedAsk,{replyCounts:1},{})
|
|
564
|
+
if (fixedJsonResult.successed){
|
|
565
|
+
answerString = fixedJsonResult.message[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
|
|
566
|
+
jsonObj = this.fixedJsonString(answerString);
|
|
567
|
+
}
|
|
568
|
+
if (!jsonObj.length) return []
|
|
569
|
+
}
|
|
510
570
|
let returnItems: QuestionItem[] = [];
|
|
511
571
|
try {
|
|
512
572
|
// let jsonObj = JSON.parse(answerString);
|
|
@@ -541,7 +601,7 @@ export default class OpenAIGpt extends GptBase {
|
|
|
541
601
|
questionitem.answer = (questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
|
|
542
602
|
break;
|
|
543
603
|
case 'multiplechoice':
|
|
544
|
-
questionitem.answer = (questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('');
|
|
604
|
+
questionitem.answer =Array.from(new Set((questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('')));
|
|
545
605
|
break;
|
|
546
606
|
case 'trueorfalse':
|
|
547
607
|
let rightItem = questionitem.choice.find((x: any) => { return x.iscorrect == 1 });
|
|
@@ -562,6 +622,8 @@ export default class OpenAIGpt extends GptBase {
|
|
|
562
622
|
///单选题的正确选项大于了1个
|
|
563
623
|
if (rightAnswer.length === 0 || !questionitem.answer || questionitem.answer.length === 0) return null;
|
|
564
624
|
}
|
|
625
|
+
///判断题验证:防止没有答案的
|
|
626
|
+
if (questiontype == 'trueorfalse' && !questionitem.answer.length ) return null;
|
|
565
627
|
|
|
566
628
|
return questionitem;
|
|
567
629
|
})
|