doomiaichat 1.4.4 → 2.0.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.
@@ -0,0 +1,95 @@
1
+ /// <reference types="node" />
2
+ import { OpenAIApi, ChatCompletionRequestMessage } from "openai";
3
+ import { EventEmitter } from "events";
4
+ import { AxiosRequestConfig } from "axios";
5
+ import { OpenAIApiParameters, ChatReponse, SummaryReponse, FaqItem, ExaminationPaperResult, EmotionResult, SimilarityResult, QuestionItem } from './declare';
6
+ export default class OpenAIGpt extends EventEmitter {
7
+ protected readonly apiKey: string;
8
+ private aiApi;
9
+ protected readonly chatModel: string;
10
+ protected readonly maxtoken: number;
11
+ protected readonly temperature: number;
12
+ /**
13
+ *
14
+ * @param apiKey 调用OpenAI 的key
15
+ * @param apiOption
16
+ */
17
+ constructor(apiKey: string, apiOption?: OpenAIApiParameters);
18
+ /**
19
+ * 初始化OpenAI 的聊天对象Api
20
+ */
21
+ createOpenAI(apiKey: string): OpenAIApi;
22
+ /**
23
+ * 向OpenAI发送一个聊天请求
24
+ * @param {*} chatText
25
+ */
26
+ chatRequest(chatText: string | Array<any>, callChatOption: OpenAIApiParameters, axiosOption?: AxiosRequestConfig): Promise<ChatReponse>;
27
+ /**
28
+ * 判断一句话的表达情绪
29
+ * @param {*} s1
30
+ * @param {*} axiosOption
31
+ */
32
+ getScentenceEmotional(s1: string, axiosOption?: any): Promise<EmotionResult>;
33
+ /**
34
+ * 获取两句话的相似度取值
35
+ * @param {*} s1
36
+ * @param {*} s2
37
+ */
38
+ getScentenseSimilarity(s1: string, s2: string, axiosOption?: any): Promise<SimilarityResult>;
39
+ /**
40
+ * 获得一种内容的相似说法
41
+ * 比如:
42
+ * 你今年多大?
43
+ * 相似问法:您是哪一年出生的
44
+ * 您今年贵庚?
45
+ * @param {*} content
46
+ * @param {需要出来的数量} count
47
+ */
48
+ getSimilarityContent(content: string, count?: number, axiosOption?: AxiosRequestConfig): Promise<ChatReponse>;
49
+ /**
50
+ * 提取内容的中心思想摘要
51
+ * @param content
52
+ * @param axiosOption
53
+ */
54
+ getSummaryOfContent(content: string | Array<any>, axiosOption?: AxiosRequestConfig): Promise<SummaryReponse>;
55
+ /**
56
+ * 从指定的文本内容中生成相关的问答
57
+ * @param {*} content
58
+ * @param {*} count
59
+ * @param {*} axiosOption
60
+ * @returns
61
+ */ generateQuestionsFromContent(content: string, count?: number, axiosOption?: AxiosRequestConfig): Promise<ChatReponse>;
62
+ /**
63
+ * 从指定的文本内容中生成相关的问答
64
+ * @param {*} content
65
+ * @param {*} count
66
+ * @param {*} axiosOption
67
+ * @returns
68
+ */ generateQuestionsFromContent_Orgin(content: string, count?: number, promotion?: string | null, sliceslength?: number, axiosOption?: AxiosRequestConfig): Promise<ChatReponse>;
69
+ /**
70
+ * 解析Faq返回的问题
71
+ * @param {*} messages
72
+ * @returns
73
+ */
74
+ protected pickUpFaqContent(messages: Array<any>): Array<FaqItem>;
75
+ /**
76
+ * 从指定的文本内容中生成一张试卷
77
+ * @param {*} content
78
+ * @param {试卷的参数} paperOption
79
+ * totalscore: 试卷总分,默认100
80
+ * section: {type:[0,1,2,3]为单选、多选、判断、填空题型 count:生成多少道 score:本段分数}
81
+ * @param {*} axiosOption
82
+ * @returns
83
+ */ generateExaminationPaperFromContent(content: string, paperOption?: any, axiosOption?: AxiosRequestConfig): Promise<ExaminationPaperResult>;
84
+ /**
85
+ * 从答复中得到题目
86
+ * @param {*} result
87
+ *
88
+ */
89
+ protected pickUpQuestions(result: Array<any>, count: number, questiontype: string, score?: number): Array<QuestionItem>;
90
+ /**
91
+ * 将一段很长的文本,按1024长度来划分到多个中
92
+ * @param {*} content
93
+ */
94
+ protected splitLongText(content: string, len?: number): Array<ChatCompletionRequestMessage>;
95
+ }
package/dist/openai.js ADDED
@@ -0,0 +1,496 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const openai_1 = require("openai");
13
+ const events_1 = require("events");
14
+ const SECTION_LENGTH = 1600; ///每2400个字符分成一组
15
+ const MESSAGE_LENGTH = 1; ///每次送8句话给openai 进行解析,送多了,会报错
16
+ //请将答案放在最后,标记为答案:()
17
+ const QUESTION_TEXT_MAPPING = {
18
+ singlechoice: '你是一名专业的出题老师,根据以下内容,生成@ITEMCOUNT@道单选题,每道题目4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题一个正确答案,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]的结构输出',
19
+ multiplechoice: '你是一名专业的出题老师,根据以下内容,请生成@ITEMCOUNT@道多选题,提供4个选项,每道题的选项中的元素用大写字母ABCD开头,每道题的答案至少有两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":[],"answer":[]}]的结构输出',
20
+ trueorfalse: '你是一名专业的出题老师,根据以下内容,请生成@ITEMCOUNT@道判断题,每道题正确和错误两个选项,输出结果必须是JSON数组并按照[{"question":"","choice":["A.正确","B.错误"],"answer":[]}]的结构输出',
21
+ completion: '你是一名专业的出题老师,根据以下内容,请生成@ITEMCOUNT@道填空题和对应答案,输出结果必须是JSON数组并按照[{"question":"","answer":["填空答案1","填空答案2"]}]的结构输出' //请将答案放在最后,标记为答案:()
22
+ };
23
+ const QUESTION_TYPE = ['singlechoice', 'multiplechoice', 'trueorfalse', 'completion'];
24
+ class OpenAIGpt extends events_1.EventEmitter {
25
+ /**
26
+ *
27
+ * @param apiKey 调用OpenAI 的key
28
+ * @param apiOption
29
+ */
30
+ constructor(apiKey, apiOption = {}) {
31
+ super();
32
+ this.apiKey = apiKey;
33
+ this.chatModel = apiOption.model || 'gpt-3.5-turbo';
34
+ this.maxtoken = apiOption.maxtoken || 2048;
35
+ this.temperature = apiOption.temperature || 0.9;
36
+ }
37
+ /**
38
+ * 初始化OpenAI 的聊天对象Api
39
+ */
40
+ createOpenAI(apiKey) {
41
+ return new openai_1.OpenAIApi(new openai_1.Configuration({ apiKey }));
42
+ }
43
+ /**
44
+ * 向OpenAI发送一个聊天请求
45
+ * @param {*} chatText
46
+ */
47
+ chatRequest(chatText, callChatOption, axiosOption = {}) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ if (!chatText)
50
+ return { successed: false, error: { errcode: 2, errmsg: '缺失聊天的内容' } };
51
+ if (!this.aiApi) {
52
+ this.aiApi = this.createOpenAI(this.apiKey);
53
+ //return { successed: false, error: { errcode: 1, errmsg: '聊天机器人无效' } };
54
+ }
55
+ let message = typeof (chatText) == 'string' ?
56
+ [{ role: 'user', content: chatText }] : chatText;
57
+ // console.log('message', message)
58
+ try {
59
+ const response = yield this.aiApi.createChatCompletion({
60
+ model: (callChatOption === null || callChatOption === void 0 ? void 0 : callChatOption.model) || this.chatModel,
61
+ messages: message,
62
+ temperature: Number((callChatOption === null || callChatOption === void 0 ? void 0 : callChatOption.temperature) || this.temperature),
63
+ max_tokens: Number((callChatOption === null || callChatOption === void 0 ? void 0 : callChatOption.maxtoken) || this.maxtoken),
64
+ n: Number((callChatOption === null || callChatOption === void 0 ? void 0 : callChatOption.replyCounts) || 1) || 1
65
+ }, axiosOption);
66
+ return { successed: true, message: response.data.choices };
67
+ }
68
+ catch (error) {
69
+ console.log('result is error ', error);
70
+ return { successed: false, error };
71
+ }
72
+ });
73
+ }
74
+ /**
75
+ * 判断一句话的表达情绪
76
+ * @param {*} s1
77
+ * @param {*} axiosOption
78
+ */
79
+ getScentenceEmotional(s1, axiosOption = { timeout: 30000 }) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ if (!s1)
82
+ return { successed: false, error: { errcode: 2, errmsg: '缺失参数' } };
83
+ const emotion = ['愤怒', '威胁', '讽刺', '愧疚', '兴奋', '友好', '消极', '生气', '正常'];
84
+ const messages = [
85
+ { role: 'user', content: s1 },
86
+ { role: 'user', content: `请分析上述内容的语言情绪,请从"${emotion.join(',')}"这些情绪中对应一个输出` },
87
+ ];
88
+ const result = yield this.chatRequest(messages, {}, axiosOption);
89
+ if (result.successed && result.message) {
90
+ let value = result.message[0].message.content.trim();
91
+ for (const word of emotion) {
92
+ if (value.indexOf(word) >= 0)
93
+ return { successed: true, emotion: word };
94
+ }
95
+ }
96
+ return { successed: true, emotion: '未知' };
97
+ });
98
+ }
99
+ /**
100
+ * 获取两句话的相似度取值
101
+ * @param {*} s1
102
+ * @param {*} s2
103
+ */
104
+ getScentenseSimilarity(s1, s2, axiosOption = { timeout: 30000 }) {
105
+ return __awaiter(this, void 0, void 0, function* () {
106
+ if (!s1 || !s2)
107
+ return { successed: false, error: { errcode: 2, errmsg: '缺失参数' } };
108
+ const messages = [
109
+ { role: 'user', content: s1 },
110
+ { role: 'user', content: s2 },
111
+ { role: 'user', content: '请从语义上对比以上两句话的相似度,请仅输出0至100之间的整数对比结果即可' },
112
+ ];
113
+ const result = yield this.chatRequest(messages, { maxtoken: 32 }, axiosOption);
114
+ if (result.successed && result.message) {
115
+ let value = result.message[0].message.content.replace(/[^\d]/g, "");
116
+ if (value > 100)
117
+ value = Math.floor(value / 10);
118
+ return { successed: true, value: Number(value) };
119
+ }
120
+ return { successed: false, error: result.error, value: 0 };
121
+ });
122
+ }
123
+ /**
124
+ * 获得一种内容的相似说法
125
+ * 比如:
126
+ * 你今年多大?
127
+ * 相似问法:您是哪一年出生的
128
+ * 您今年贵庚?
129
+ * @param {*} content
130
+ * @param {需要出来的数量} count
131
+ */
132
+ getSimilarityContent(content, count = 1, axiosOption = {}) {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ let chnReg = /([\u4e00-\u9fa5]|[\ufe30-\uffa0])/.test(content); ///检查源话是否含有中文内容
135
+ let engReg = /[a-zA-Z]/.test(content); ///检查源话是否含有英文内容
136
+ ///如果源话是全中文,那么结果中不应该出来英文的相似说法,如果源话是全英文,则结果不能出现全中文的说法
137
+ let prefix = (!chnReg && engReg) ? '请用完整的英文表达,' : ((chnReg && !engReg) ? '请用完整的中文表达,' : '');
138
+ const text = `${prefix}生成与下面句子意思相同的内容"${content}"`;
139
+ let result = yield this.chatRequest(text, { replyCounts: count }, axiosOption);
140
+ if (!result.successed || !result.message)
141
+ return result;
142
+ let replys = result.message.map(item => { return item.message.content.trim(); });
143
+ return { successed: true, message: replys };
144
+ });
145
+ }
146
+ /**
147
+ * 提取内容的中心思想摘要
148
+ * @param content
149
+ * @param axiosOption
150
+ */
151
+ getSummaryOfContent(content, axiosOption = {}) {
152
+ return __awaiter(this, void 0, void 0, function* () {
153
+ const arrContent = typeof (content) == 'string' ? this.splitLongText(content) : content;
154
+ let summary = [];
155
+ while (arrContent.length > 0) {
156
+ let subarray = arrContent.slice(0, MESSAGE_LENGTH);
157
+ subarray.push({ role: 'user', content: '根据上述内容精简,提炼内容提纲及摘要内容,每项摘要内容尽量精简数据化不超过100字,结果严格按照[{outline:"提纲标题","summary":["摘要内容1","摘要内容2","摘要内容3"]}]的JSON结构输出' });
158
+ let result = yield this.chatRequest(subarray, {}, axiosOption);
159
+ if (result.successed && result.message) {
160
+ try {
161
+ // console.log('result.message[0].content', result.message[0].message.content)
162
+ let jsonObjItems = JSON.parse(result.message[0].message.content);
163
+ if (Array.isArray(jsonObjItems))
164
+ summary = summary.concat(jsonObjItems);
165
+ }
166
+ catch (error) {
167
+ console.log('result.message[0].content', error);
168
+ }
169
+ }
170
+ ////删除已经处理的文本
171
+ arrContent.splice(0, MESSAGE_LENGTH);
172
+ }
173
+ return { successed: true, article: summary };
174
+ });
175
+ }
176
+ /**
177
+ * 从指定的文本内容中生成相关的问答
178
+ * @param {*} content
179
+ * @param {*} count
180
+ * @param {*} axiosOption
181
+ * @returns
182
+ */ //并在答案末尾处必须给出答案内容中的关键词
183
+ generateQuestionsFromContent(content, count = 1, axiosOption = {}) {
184
+ return __awaiter(this, void 0, void 0, function* () {
185
+ let arrContent = this.splitLongText(content, 300);
186
+ ///没20句话分为一组,适应大文件内容多次请求组合结果
187
+ ///每一句话需要产生的题目
188
+ let questions4EverySentense = count / arrContent.length; //Math.ceil(arrContent.length / 20);
189
+ let faqs = [], gotted = 0;
190
+ while (arrContent.length > 0 && gotted < count) {
191
+ questions4EverySentense = (count - gotted) / arrContent.length;
192
+ ////每次最多送MESSAGE_LENGTH句话给openai
193
+ let subarray = arrContent.slice(0, 4);
194
+ let itemCount = Math.min(Math.ceil(subarray.length * questions4EverySentense), count - gotted);
195
+ //subarray.push({ role: 'user', content:'请根据上述内容,给出一道提问与答案以及答案关键词,按照先问题内容,再标准答案,再关键词的顺序输出,关键词之间用、分开'})
196
+ subarray.unshift({ role: 'system', content: `你是一位专业培训师,从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON数组结构输出。` });
197
+ //subarray.unshift({ role: 'system', content: `你是一位专业程序开发工程师,根据以下内容,按照[{"question":"问题内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]JSON数组结构,给出${itemCount}条提问问题及答案以及答案关键词` })
198
+ console.log('subarray', subarray);
199
+ let result = yield this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
200
+ if (result.successed && result.message) {
201
+ // console.log('result is ', result.message[0].message.content)
202
+ let msgs = this.pickUpFaqContent(result.message);
203
+ if (msgs.length) {
204
+ ///对外发送检出问答题的信号
205
+ this.emit('parseout', { type: 'qa', items: msgs });
206
+ gotted += msgs.length; //result.message.length;
207
+ faqs = faqs.concat(msgs);
208
+ }
209
+ }
210
+ ////删除已经处理的文本
211
+ arrContent.splice(0, 4);
212
+ }
213
+ arrContent = []; /// 释放内存
214
+ ///发出信号,解析完毕
215
+ this.emit('parseover', { type: 'qa', items: faqs });
216
+ return { successed: true, message: faqs };
217
+ });
218
+ }
219
+ /**
220
+ * 从指定的文本内容中生成相关的问答
221
+ * @param {*} content
222
+ * @param {*} count
223
+ * @param {*} axiosOption
224
+ * @returns
225
+ */ //并在答案末尾处必须给出答案内容中的关键词
226
+ generateQuestionsFromContent_Orgin(content, count = 1, promotion = null, sliceslength = SECTION_LENGTH, axiosOption = {}) {
227
+ return __awaiter(this, void 0, void 0, function* () {
228
+ let arrContent = this.splitLongText(content, sliceslength || 300);
229
+ ///没20句话分为一组,适应大文件内容多次请求组合结果
230
+ ///每一句话需要产生的题目
231
+ let questions4EverySentense = count / arrContent.length; //Math.ceil(arrContent.length / 20);
232
+ let faqs = [], gotted = 0;
233
+ while (arrContent.length > 0 && gotted < count) {
234
+ questions4EverySentense = (count - gotted) / arrContent.length;
235
+ ////每次最多送MESSAGE_LENGTH句话给openai
236
+ let subarray = arrContent.slice(0, 4);
237
+ let itemCount = Math.min(Math.ceil(subarray.length * questions4EverySentense), count - gotted);
238
+ subarray.unshift({ role: 'system', content: promotion || `你是一位专业培训师,从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON数组结构输出。` });
239
+ let result = yield this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
240
+ if (result.successed && result.message) {
241
+ let answer = result.message.map(i => { return i.message.content.trim().replace(/\t|\n|\v|\r|\f/g, ''); });
242
+ faqs = faqs.concat(answer);
243
+ }
244
+ ////删除已经处理的文本
245
+ arrContent.splice(0, 4);
246
+ }
247
+ arrContent = []; /// 释放内存
248
+ return { successed: true, message: faqs };
249
+ });
250
+ }
251
+ /**
252
+ * 解析Faq返回的问题
253
+ * @param {*} messages
254
+ * @returns
255
+ */
256
+ pickUpFaqContent(messages) {
257
+ let answerString = messages[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
258
+ const firstsybmol = answerString.indexOf("["); ////必须过滤出来数组
259
+ const lastsybmol = answerString.lastIndexOf("]");
260
+ console.log('answerString', answerString);
261
+ if (firstsybmol < 0 || lastsybmol < 0 || lastsybmol <= firstsybmol)
262
+ return [];
263
+ answerString = answerString.substr(firstsybmol, lastsybmol - firstsybmol + 1);
264
+ console.log('answerString', answerString);
265
+ try {
266
+ //let jsonObj = JSON.parse(answerString);
267
+ let jsonObj = eval(answerString);
268
+ jsonObj.map((item) => {
269
+ let realKeyword = [];
270
+ let keywords = (item.keywords + '').split(',');
271
+ let answer = item.answer || '';
272
+ for (const k of keywords) {
273
+ if (k && answer.indexOf(k) >= 0)
274
+ realKeyword.push(k);
275
+ }
276
+ item.keywords = realKeyword;
277
+ return item;
278
+ });
279
+ return jsonObj;
280
+ }
281
+ catch (err) {
282
+ console.log('JSON error', err);
283
+ return [];
284
+ }
285
+ }
286
+ /**
287
+ * 从指定的文本内容中生成一张试卷
288
+ * @param {*} content
289
+ * @param {试卷的参数} paperOption
290
+ * totalscore: 试卷总分,默认100
291
+ * section: {type:[0,1,2,3]为单选、多选、判断、填空题型 count:生成多少道 score:本段分数}
292
+ * @param {*} axiosOption
293
+ * @returns
294
+ */ //并在答案末尾处必须给出答案内容中的关键词
295
+ generateExaminationPaperFromContent(content, paperOption = {}, axiosOption = {}) {
296
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
297
+ return __awaiter(this, void 0, void 0, function* () {
298
+ let arrContent = this.splitLongText(content, 1024);
299
+ let sectionCount = {
300
+ singlechoice: (((_a = paperOption.singlechoice) === null || _a === void 0 ? void 0 : _a.count) || 0) / arrContent.length,
301
+ multiplechoice: (((_b = paperOption.multiplechoice) === null || _b === void 0 ? void 0 : _b.count) || 0) / arrContent.length,
302
+ trueorfalse: (((_c = paperOption.trueorfalse) === null || _c === void 0 ? void 0 : _c.count) || 0) / arrContent.length,
303
+ completion: (((_d = paperOption.completion) === null || _d === void 0 ? void 0 : _d.count) || 0) / arrContent.length
304
+ };
305
+ ///剩余待生成的题目数量
306
+ let remainCount = {
307
+ singlechoice: ((_e = paperOption.singlechoice) === null || _e === void 0 ? void 0 : _e.count) || 0,
308
+ multiplechoice: ((_f = paperOption.multiplechoice) === null || _f === void 0 ? void 0 : _f.count) || 0,
309
+ trueorfalse: ((_g = paperOption.trueorfalse) === null || _g === void 0 ? void 0 : _g.count) || 0,
310
+ completion: ((_h = paperOption.completion) === null || _h === void 0 ? void 0 : _h.count) || 0
311
+ };
312
+ ///每种类型的题目的分数
313
+ let ITEM_SCORE = {
314
+ singlechoice: ((_j = paperOption.singlechoice) === null || _j === void 0 ? void 0 : _j.score) || 0,
315
+ multiplechoice: ((_k = paperOption.multiplechoice) === null || _k === void 0 ? void 0 : _k.score) || 0,
316
+ trueorfalse: ((_l = paperOption.trueorfalse) === null || _l === void 0 ? void 0 : _l.score) || 0,
317
+ completion: ((_m = paperOption.completion) === null || _m === void 0 ? void 0 : _m.score) || 0
318
+ };
319
+ ///最后生成出来的结果
320
+ let paperReturned = {
321
+ singlechoice: [], multiplechoice: [], trueorfalse: [], completion: []
322
+ }, noMoreQuestionRetrive = false, totalscore = 0;
323
+ while (arrContent.length > 0 && !noMoreQuestionRetrive) {
324
+ ////每次最多送MESSAGE_LENGTH句话给openai
325
+ let subarray = arrContent.slice(0, MESSAGE_LENGTH);
326
+ /**
327
+ * 每种类型的题目进行遍历
328
+ */
329
+ noMoreQuestionRetrive = true;
330
+ for (const key of QUESTION_TYPE) {
331
+ ///还需要抓取题目
332
+ if (remainCount[key] > 0) {
333
+ noMoreQuestionRetrive = false;
334
+ let itemCount = Math.min(remainCount[key], Math.ceil(subarray.length * sectionCount[key]));
335
+ subarray.unshift({ role: 'system', content: QUESTION_TEXT_MAPPING[key].replace('@ITEMCOUNT@', itemCount) });
336
+ let result = yield this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
337
+ if (result.successed && result.message) {
338
+ //console.log('paper result', key, result.message.length)
339
+ let pickedQuestions = this.pickUpQuestions(result.message, itemCount, key, ITEM_SCORE[key]);
340
+ if (pickedQuestions.length) {
341
+ ///对外发送检出题目的信号
342
+ this.emit('parseout', { type: 'question', name: key, items: pickedQuestions });
343
+ paperReturned[key] = paperReturned[key].concat(pickedQuestions);
344
+ remainCount[key] = remainCount[key] - pickedQuestions.length;
345
+ totalscore = totalscore + pickedQuestions.length * ITEM_SCORE[key];
346
+ }
347
+ }
348
+ subarray.splice(0, 1); ///把第一个角色定位的问法删除
349
+ // subarray.splice(subarray.length - 1, 1); ///把第一个角色定位的问法删除
350
+ }
351
+ }
352
+ ////删除已经处理的文本
353
+ arrContent.splice(0, MESSAGE_LENGTH);
354
+ }
355
+ ///发出信号,解析完毕
356
+ this.emit('parseover', { type: 'question', items: paperReturned });
357
+ return { successed: true, score: totalscore, paper: paperReturned };
358
+ });
359
+ }
360
+ /**
361
+ * 从答复中得到题目
362
+ * @param {*} result
363
+ *
364
+ */
365
+ pickUpQuestions(result, count, questiontype, score = 1) {
366
+ let answerString = result[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
367
+ const firstsybmol = answerString.indexOf("["); ////必须过滤出来数组
368
+ const lastsybmol = answerString.lastIndexOf("]");
369
+ console.log('answerString', answerString);
370
+ if (firstsybmol < 0 || lastsybmol < 0 || lastsybmol <= firstsybmol)
371
+ return [];
372
+ answerString = answerString.substr(firstsybmol, lastsybmol - firstsybmol + 1);
373
+ console.log('answerString', answerString);
374
+ let returnItems = [];
375
+ try {
376
+ let jsonObj = JSON.parse(answerString);
377
+ returnItems = jsonObj.map((questionitem) => {
378
+ console.log('answer item', questionitem);
379
+ if (questionitem.choice && Array.isArray(questionitem.choice) && questiontype != 'completion') {
380
+ questionitem.fullanswer = (questionitem.answer + '').replace(/,|[^ABCDE]/g, '');
381
+ questionitem.score = score;
382
+ if (questionitem.choice) {
383
+ questionitem.choice = questionitem.choice.map((item, index) => {
384
+ let seqNo = String.fromCharCode(65 + index);
385
+ let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig');
386
+ //let answer = jsonObj.fullanswer
387
+ return {
388
+ id: seqNo,
389
+ content: item.replace(correctReg, '').trim(),
390
+ iscorrect: (questionitem.fullanswer || '').indexOf(seqNo) >= 0 ? 1 : 0
391
+ //|| jsonObj.fullanswer.indexOf(m))
392
+ };
393
+ });
394
+ }
395
+ ///如果是非判断题,题目的选项数量小于2 ,则无效
396
+ ///如果是判断题,题目的选项必须=2
397
+ if (!questionitem.choice || (questiontype != 'trueorfalse' && questionitem.choice.length < 3) || (questiontype == 'trueorfalse' && questionitem.choice.length != 2)) {
398
+ return null;
399
+ }
400
+ }
401
+ switch (questiontype) {
402
+ case 'singlechoice':
403
+ questionitem.answer = (questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
404
+ break;
405
+ case 'multiplechoice':
406
+ questionitem.answer = (questionitem.answer + '').replace(/,|[^ABCDEFG]/g, '').split('');
407
+ break;
408
+ case 'trueorfalse':
409
+ questionitem.answer = [(questionitem.answer + '').indexOf('正确') >= 0 ? 'A' : 'B'];
410
+ break;
411
+ }
412
+ ///单选题验证
413
+ if (questiontype == 'singlechoice') {
414
+ let rightAnswer = questionitem.choice ? questionitem.choice.filter(item => { return item.iscorrect === 1; }) : [];
415
+ ///单选题的正确选项大于了1个
416
+ if (rightAnswer.length != 1 || !questionitem.answer || questionitem.answer.length !== 1)
417
+ return null;
418
+ ///正确选项和答案不一致
419
+ if (rightAnswer[0].id.toUpperCase() != (questionitem.answer[0] || '').toUpperCase())
420
+ return null;
421
+ }
422
+ ///多选题验证
423
+ if (questiontype == 'multiplechoice') {
424
+ let rightAnswer = questionitem.choice ? questionitem.choice.filter(item => { return item.iscorrect === 1; }) : [];
425
+ ///单选题的正确选项大于了1个
426
+ if (rightAnswer.length === 0 || !questionitem.answer || questionitem.answer.length === 0)
427
+ return null;
428
+ }
429
+ return questionitem;
430
+ });
431
+ }
432
+ catch (err) {
433
+ console.log('error happened:', err);
434
+ }
435
+ return returnItems.filter(i => { return i != null; }).slice(0, count);
436
+ // let item = result.map(m => {
437
+ // ////防止输出的JSON格式不合法
438
+ // try {
439
+ // let jsonObj = JSON.parse(m.message.content)
440
+ // jsonObj.score = score;
441
+ // if (jsonObj.choice && Array.isArray(jsonObj.choice) && questiontype != 'completion') {
442
+ // jsonObj.fullanswer = (jsonObj.answer + '').replace(/,|[^ABCDE]/g, '');
443
+ // jsonObj.choice = jsonObj.choice.map((item:string, index:number) => {
444
+ // let seqNo = String.fromCharCode(65 + index);
445
+ // let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig')
446
+ // //let answer = jsonObj.fullanswer
447
+ // return {
448
+ // id: seqNo,
449
+ // content: item.replace(correctReg, '').trim(),
450
+ // iscorrect: (jsonObj.fullanswer.indexOf(seqNo) >= 0 || jsonObj.fullanswer.indexOf(m)) >= 0 ? 1 : 0
451
+ // }
452
+ // })
453
+ // }
454
+ // switch (questiontype) {
455
+ // case 'singlechoice':
456
+ // jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
457
+ // break;
458
+ // case 'multiplechoice':
459
+ // jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('');
460
+ // break;
461
+ // case 'trueorfalse':
462
+ // jsonObj.answer = [(jsonObj.answer + '').indexOf('正确') >= 0 ? 'A' : 'B']
463
+ // break;
464
+ // }
465
+ // return jsonObj;
466
+ // } catch (err) {
467
+ // console.log('error happened:', err);
468
+ // return null;
469
+ // }
470
+ // })
471
+ // return item.filter(i => { return i != null; });
472
+ }
473
+ /**
474
+ * 将一段很长的文本,按1024长度来划分到多个中
475
+ * @param {*} content
476
+ */
477
+ splitLongText(content, len = SECTION_LENGTH) {
478
+ let start = 0, message = [], length = content.length;
479
+ while (start < length) {
480
+ let realLength = len;
481
+ ////以句号或引号进行分段,不要随意截取
482
+ for (let i = start + len; i >= start; i--) {
483
+ if (/[。”"??]/.test(content[i] + '')) {
484
+ realLength = i - start + 1;
485
+ break;
486
+ }
487
+ }
488
+ const subtext = content.substr(start, realLength);
489
+ if (subtext)
490
+ message.push({ role: 'user', content: subtext });
491
+ start += realLength || len;
492
+ }
493
+ return message;
494
+ }
495
+ }
496
+ exports.default = OpenAIGpt;
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "doomiaichat",
3
- "version": "1.4.4",
3
+ "version": "2.0.0",
4
4
  "description": "Doomisoft OpenAI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "build": "tsc"
8
8
  },
9
- "keywords": ["ChatGpt"],
9
+ "keywords": [
10
+ "ChatGpt"
11
+ ],
10
12
  "author": "Stephen.Shen",
11
13
  "license": "ISC",
12
14
  "devDependencies": {
13
- "@types/node": "^18.15.0",
15
+ "@types/node": "^18.15.11",
14
16
  "typescript": "^4.9.5"
15
17
  },
16
18
  "dependencies": {
package/src/azureai.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { AzureOpenAIPatameters, ChatReponse, OpenAIApiParameters } from "./declare";
2
+ import { AxiosRequestConfig, AxiosResponse, Axios } from "axios";
3
+ import OpenAIGpt from "./openai"
4
+ import { ChatCompletionRequestMessage } from "openai";
5
+ export default class AzureAI extends OpenAIGpt {
6
+ protected readonly apiKey: string;
7
+ protected readonly azureSetting: AzureOpenAIPatameters;
8
+ constructor(apiKey: string, azureOption:AzureOpenAIPatameters, apiOption: OpenAIApiParameters = {}) {
9
+ super(apiKey, apiOption);
10
+ this.azureSetting = azureOption;
11
+ if (!this.azureSetting.endpoint.toLowerCase().startsWith('https://') &&
12
+ !this.azureSetting.endpoint.toLowerCase().startsWith('https://')){
13
+ this.azureSetting.endpoint = 'https://' + this.azureSetting.endpoint;
14
+ }
15
+ }
16
+ /**
17
+ * ZAure OpenAI 最新的URL地址
18
+ */
19
+ get BaseUrl():string{
20
+ return `${this.azureSetting.endpoint}/openai/deployments/${this.azureSetting.engine}/chat/completions?api-version=${this.azureSetting.version ||'2023-03-15-preview'}`
21
+ }
22
+ /**
23
+ * 请求GPT接口
24
+ */
25
+ public async chatRequest(chatText: string | Array<any>, paramOption: OpenAIApiParameters, axiosOption: AxiosRequestConfig = {}): Promise<ChatReponse> {
26
+ if (!chatText) return { successed: false, error: { errcode: 2, errmsg: '缺失聊天的内容' } };
27
+ if (!axiosOption.headers )
28
+ axiosOption.headers = { 'api-key': this.apiKey,'Content-Type':'application/json'};
29
+ else{
30
+ axiosOption.headers['api-key']=this.apiKey;
31
+ axiosOption.headers['Content-Type'] = 'application/json';
32
+ }
33
+
34
+ let messages: Array<ChatCompletionRequestMessage> = typeof (chatText) == 'string' ?
35
+ [{ role: 'user', content: chatText }] : chatText;
36
+ let axios = new Axios(axiosOption)
37
+ try{
38
+ const response = await axios.post(this.BaseUrl, {
39
+ messages,
40
+ temperature: Number(paramOption?.temperature || this.temperature),
41
+ max_tokens: Number(paramOption?.maxtoken || this.maxtoken),
42
+ })
43
+ if (response.data.choices){
44
+ return { successed: true, message: response.data.choices };
45
+ }
46
+ return { successed: false, ...response.data };
47
+ } catch (error) {
48
+ console.log('result is error ', error)
49
+ return { successed: false, error };
50
+ }
51
+
52
+ }
53
+ }