tmaiplugin
Version:
TrainingMaster AIGC Component
304 lines (303 loc) • 19.4 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuestionPlugin = void 0;
const stringutil_1 = require("./util/stringutil");
const aipluginbase_1 = __importDefault(require("./aipluginbase"));
const ROLE_DEFINE = [
'你是一位精通各行业的技能考试专家,擅长根据文档内容提取重点要点并形成考核问题、相关选项及正确答案',
'你是一位程序设计专家,特别擅长数据分析,内容组织并进行结构化设计,形成Json格式的数据'
];
const QUESTION_TYPE = ['singlechoice', 'multiplechoice', 'trueorfalse', 'completion'];
const PROMPT_LINK = {
singlechoice: [
`根据以下内容提炼{{COUNT}}道单选题。要求如下:
1、问题需要与所给的资料相关,绝不能问超出所给资料的范围及自己捏造;所提问题需要准确、完整、清晰,绝不能有歧义;意思相近的问题不要重复给出;
2、每道题目4个选项,选项中有且仅有一个正确选项;
3、生成问题的时候,请出具有代表性的问题,对于一些无关紧要的问题可以忽略。特别注意资料中关于数字、参数、特点等关键信息的提取,在给出的问题中尽可能覆盖;
4、输出结果包括:题目内容、考题选项、答案;
5、如果无法从内容中提炼任何问题,仅需返回"NO"。
内容如下:"""
{{CONTENT}}
"""`,
`请将以下内容去除题目序号,按照[{"question":"","choice":[],"answer":[]}]的标准Json数组结构输出。
内容如下:"""
{{CONTENT}}
"""`
],
multiplechoice: [
`根据以下内容提炼{{COUNT}}道多选题。要求如下:
1、问题需要与所给的资料相关,绝不能问超出所给资料的范围及自己捏造;所提问题需要准确、完整、清晰,绝不能有歧义;意思相近的问题不要重复给出;
2、每道题目4个选项,选项中至少两个或以上正确选项;
3、生成问题的时候,请出具有代表性的问题,对于一些无关紧要的问题可以忽略。特别注意资料中关于数字、参数、特点等关键信息的提取,在给出的问题中尽可能覆盖;
4、输出结果包括:题目内容、考题选项、答案;
5、如果无法从内容中提炼任何问题,仅需返回"NO"。
内容如下:"""
{{CONTENT}}
"""`,
`请将以下内容去除题目序号,按照[{"question":"","choice":[],"answer":[]}]的标准Json数组结构输出。
内容如下:"""
{{CONTENT}}
"""`
],
trueorfalse: [
`根据以下内容提炼{{COUNT}}道判断题。要求如下:
1、问题需要与所给的资料相关,绝不能问超出所给资料的范围及自己捏造;所提问题需要准确、完整、清晰,绝不能有歧义;意思相近的问题不要重复给出;
2、请出具有代表性的问题,对于一些无关紧要的问题可以忽略。特别注意资料中关于数字、参数、特点等关键信息的提取,在给出的问题中尽可能覆盖;
3、绝对不能在题目中包含答案,判断题答案选项固定为:"正确"、"错误"。
4、输出结果包括:题目内容、选项、答案;
5、如果无法从内容中提炼任何问题,仅需返回"NO"。
内容如下:"""
{{CONTENT}}
"""`,
`请将以下内容去除题目序号,按照[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]的标准Json数组结构输出。
内容如下:"""
{{CONTENT}}
"""`
],
completion: [
`根据以下内容提炼{{COUNT}}道填空题。要求如下:
1、问题需要与所给的资料相关,绝不能问超出所给资料的范围及自己捏造;所提问题需要准确、完整、清晰,绝不能有歧义;意思相近的问题不要重复给出;
2、每题至少一个或以上的填空,尽量将填空的内容放置于题目中间,并在内容中用“____”为每个填空预留标记;
3、如填空项不能预留在题目内容中,则在题目内容结尾处添加对应填空数量的“____”;
4、填空务必简短,每个空的内容长度绝对不超过10个中文字或3个英文单词;
5、生成问题的时候,请出具有代表性的问题,对于一些无关紧要的问题可以忽略。特别注意资料中关于数字、参数、特点等关键信息的提取,在给出的问题中尽可能覆盖;
6、输出结果包括:题目内容、填空答案;
7、如果无法从内容中提炼任何问题,仅需返回"NO"。
内容如下:"""
{{CONTENT}}
"""`,
`请将以下内容去除题目序号,按照[{"question":"","answer":["填空1","填空2"]}]的标准Json数组结构输出。
内容如下:"""
{{CONTENT}}
"""`
]
};
/**
* Faq问题的提取器插件
*/
class QuestionPlugin extends aipluginbase_1.default {
/**
* 从指定的文本内容中生成相关的问答
* @param {*} content
* @param {*} paper
* @param {*} axios
* @returns
*/ //并在答案末尾处必须给出答案内容中的关键词
execute(params) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
return __awaiter(this, void 0, void 0, function* () {
let { content, paper: paperOption, sectionlength, axios } = params;
sectionlength = sectionlength || 1024;
if (!this.gptInstance)
return { successed: false };
let arrContent = (0, stringutil_1.splitLongText)(content, sectionlength);
if (!arrContent.length)
return { successed: false };
let sectionCount = {
singlechoice: (((_a = paperOption.singlechoice) === null || _a === void 0 ? void 0 : _a.count) || 0) / arrContent.length,
multiplechoice: (((_b = paperOption.multiplechoice) === null || _b === void 0 ? void 0 : _b.count) || 0) / arrContent.length,
trueorfalse: (((_c = paperOption.trueorfalse) === null || _c === void 0 ? void 0 : _c.count) || 0) / arrContent.length,
completion: (((_d = paperOption.completion) === null || _d === void 0 ? void 0 : _d.count) || 0) / arrContent.length
};
///剩余待生成的题目数量
let remainCount = {
singlechoice: ((_e = paperOption.singlechoice) === null || _e === void 0 ? void 0 : _e.count) || 0,
multiplechoice: ((_f = paperOption.multiplechoice) === null || _f === void 0 ? void 0 : _f.count) || 0,
trueorfalse: ((_g = paperOption.trueorfalse) === null || _g === void 0 ? void 0 : _g.count) || 0,
completion: ((_h = paperOption.completion) === null || _h === void 0 ? void 0 : _h.count) || 0
};
///每种类型的题目的分数
let ITEM_SCORE = {
singlechoice: ((_j = paperOption.singlechoice) === null || _j === void 0 ? void 0 : _j.score) || 0,
multiplechoice: ((_k = paperOption.multiplechoice) === null || _k === void 0 ? void 0 : _k.score) || 0,
trueorfalse: ((_l = paperOption.trueorfalse) === null || _l === void 0 ? void 0 : _l.score) || 0,
completion: ((_m = paperOption.completion) === null || _m === void 0 ? void 0 : _m.score) || 0
};
///最后生成出来的结果
let paperReturned = {
singlechoice: [], multiplechoice: [], trueorfalse: [], completion: []
}, noMoreQuestionRetrive = false, totalscore = 0;
while (arrContent.length > 0 && !noMoreQuestionRetrive) {
/**
* 每种类型的题目进行遍历
*/
noMoreQuestionRetrive = true;
for (const key of QUESTION_TYPE) {
console.log('key', key);
///还需要抓取题目
if (remainCount[key] > 0) {
noMoreQuestionRetrive = false;
//let itemCount = Math.min(remainCount[key], Math.ceil(subarray.length * sectionCount[key]));
let itemCount = Math.min(remainCount[key], Math.ceil(sectionCount[key]));
let subarray = [
{ role: 'system', content: ROLE_DEFINE[0] },
{ role: 'user', content: PROMPT_LINK[key][0].replace('{{COUNT}}', itemCount).replace('{{CONTENT}}', arrContent.slice(0, 1)[0]) },
// { role: 'user', content:`出题材料:${arrContent.slice(0, 1)[0]}`}
];
// subarray.unshift()
console.log('subarray', subarray);
let result = yield this.gptInstance.chatRequest(subarray, { replyCounts: 1 }, axios);
///如果请求发生了网络错误(不是内容合规问题),则再重试一次,如果任然有错则放弃
if (!result.successed && result.error != 'content_filter') {
console.log('network error,retry onemore time');
result = yield this.gptInstance.chatRequest(subarray, { replyCounts: 1 }, axios);
}
console.log('subarray returned', result.successed);
if (result.successed && result.message) {
let pickedQuestions = yield this.pickUpQuestions(result.message, itemCount, key, ITEM_SCORE[key]);
if (pickedQuestions.length) {
///对外发送检出题目的信号
this.emit('parseout', { type: 'question', name: key, items: pickedQuestions });
paperReturned[key] = paperReturned[key].concat(pickedQuestions);
remainCount[key] = remainCount[key] - pickedQuestions.length;
totalscore = totalscore + pickedQuestions.length * ITEM_SCORE[key];
}
}
}
}
////删除已经处理的文本
arrContent.splice(0, 1);
}
console.log('parseover');
///发出信号,解析完毕
this.emit('parseover', { type: 'question', items: paperReturned });
return { successed: true, score: totalscore, paper: paperReturned };
});
}
/**
* 从答复中得到题目
* @param {*} result
*
*/
pickUpQuestions(result, count, questiontype, score = 1) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
if (!this.gptInstance || !((_b = (_a = result[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content))
return [];
let answerString = result[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
if (answerString.includes('NO'))
return [];
let orgJsonPrompt = [
{ role: 'system', content: ROLE_DEFINE[1] },
{ role: 'user', content: PROMPT_LINK[questiontype][1].replace('{{CONTENT}}', answerString) }
];
console.log('orgJsonPrompt', orgJsonPrompt);
let fixedJsonResult = yield this.gptInstance.chatRequest(orgJsonPrompt, { replyCounts: 1 }, {});
if (fixedJsonResult.successed) {
answerString = fixedJsonResult.message[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
}
let jsonObj = fixedJsonResult.successed ? (0, stringutil_1.fixedJsonString)(answerString) : [];
let returnItems = [];
const chineseReg = new RegExp("[\\u4E00-\\u9FFF]+", "g");
try {
returnItems = jsonObj.map((questionitem) => {
console.log('answer item from jsonObj', questionitem);
if (!questionitem.question)
return null;
///避免内容最前面出现 题目1:
questionitem.question = questionitem.question.trim().replace(/^题目\s*\d*\s*(:|:)*/, '').replace(/\s*(\(|()*(单选|多选|判断|填空)(\)|))*\s*$/, '');
if (questionitem.choice && Array.isArray(questionitem.choice) && questiontype != 'completion') {
questionitem.fullanswer = (questionitem.answer + '').replace(/,|[^ABCDE]/g, '');
questionitem.score = score;
if (questionitem.choice) {
questionitem.choice = questionitem.choice.map((item, index) => {
let seqNo = 'ABCDEFG'[index]; //String.fromCharCode(65 + index);
let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig');
return {
id: seqNo,
content: (item + '').replace(correctReg, '').trim().replace(/^选项\s*\d*\s*(:|:)*/, ''),
iscorrect: (questionitem.fullanswer || '').indexOf(seqNo) >= 0 ? 1 : 0
};
});
}
///如果是非判断题,题目的选项数量小于2 ,则无效
///如果是判断题,题目的选项必须=2
if (!questionitem.choice || (questiontype != 'trueorfalse' && questionitem.choice.length < 3) || (questiontype == 'trueorfalse' && questionitem.choice.length != 2)) {
return null;
}
}
switch (questiontype) {
case 'singlechoice':
questionitem.answer = (questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
break;
case 'multiplechoice':
questionitem.answer = Array.from(new Set((questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split(''))).sort();
////多选题选项少于2个的,也不要了
if (questionitem.answer.length < 2)
return null;
break;
case 'trueorfalse':
if (questionitem.choice.length != 2)
return null;
////选项必须是正确或错误,否者这道题不要了
if (!['正确', '错误'].includes(questionitem.choice[0].content))
return null;
let rightItem = questionitem.choice.find((x) => { return x.iscorrect == 1; });
questionitem.answer = [(rightItem === null || rightItem === void 0 ? void 0 : rightItem.id) || 'Z']; //[(questionitem.answer + '').indexOf('正确') >= 0 ? 'A' : 'B']
///防止题目内容中直接出现了答案内容
questionitem.question = questionitem.question.replace(/(答案){0,1}(:|:)(-){0,5}\s?(\(|(){0,1}\s{0,5}(正确|错误|正确\/错误|错误\/正确){0,1}(\)|)){0,1}(\/){0,1}(.|。|;|;){0,1}$/, '').trim().replace(/(,|,)$/, '。');
break;
case 'completion':
let verylong = questionitem.answer.filter((item) => {
///如果回答内容不包括英文,则填空题的长度不能超过15
if (chineseReg.test(item))
return item.length >= 15;
////如果包含了英文,则不超过40个长度
return item.length >= 30;
});
////如果填空题的内容超长,这道题也不要
if (verylong.length > 0)
return null;
///如果填空题没有提空答案,不要
if (!questionitem.answer || questionitem.answer.length == 0)
return null;
questionitem.answer = questionitem.answer.map((item) => {
return item.replace(/([-"“”’',,。??、!!~*; ])/g, '');
});
////去除填空题尾部可能出来的杂项
questionitem.question = questionitem.question.replace(/(\(|().{0,1}(个){0,1}(\)|)){0,1}(.|。){0,1}$/, '');
break;
}
///单选题验证
if (questiontype == 'singlechoice') {
let rightAnswer = questionitem.choice ? questionitem.choice.filter((item) => { return item.iscorrect === 1; }) : [];
///单选题的正确选项大于了1个
if (rightAnswer.length != 1 || !questionitem.answer || questionitem.answer.length !== 1)
return null;
///正确选项和答案不一致
if (rightAnswer[0].id.toUpperCase() != (questionitem.answer[0] || '').toUpperCase())
return null;
}
///多选题验证
if (questiontype == 'multiplechoice') {
let rightAnswer = questionitem.choice ? questionitem.choice.filter((item) => { return item.iscorrect === 1; }) : [];
///单选题的正确选项大于了1个
if (rightAnswer.length === 0 || !questionitem.answer || questionitem.answer.length === 0)
return null;
}
///判断题验证:防止没有答案的
if (questiontype == 'trueorfalse' && !questionitem.answer.length)
return null;
return questionitem;
});
}
catch (err) {
console.log('error happened:', err);
}
return returnItems.filter(i => { return i != null; }).slice(0, count);
});
}
}
exports.QuestionPlugin = QuestionPlugin;