doomiaichat 2.1.0 → 2.3.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/src/openai.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Configuration, OpenAIApi, ChatCompletionRequestMessage } from "openai"
2
- import { EventEmitter } from "events";
3
- import { OpenAIApiParameters, ChatReponse, OutlineSummaryItem, SummaryReponse, FaqItem, ExaminationPaperResult, EmotionResult, SimilarityResult, QuestionItem } from './declare'
2
+ // import { EventEmitter } from "events";
3
+ import GptBase from "./gptbase"
4
+ import { OpenAIApiParameters, ChatReponse, OutlineSummaryItem, SummaryReponse, FaqItem, ExaminationPaperResult, EmotionResult, SimilarityResult, QuestionItem, CommentResult } from './declare'
4
5
  const SECTION_LENGTH = 1600; ///每2400个字符分成一组
5
6
  const MESSAGE_LENGTH = 1; ///每次送8句话给openai 进行解析,送多了,会报错
6
7
  //请将答案放在最后,标记为答案:()
@@ -13,7 +14,7 @@ const QUESTION_TEXT_MAPPING: any = {
13
14
 
14
15
  const QUESTION_TYPE: string[] = ['singlechoice', 'multiplechoice', 'trueorfalse', 'completion']
15
16
 
16
- export default class OpenAIGpt extends EventEmitter {
17
+ export default class OpenAIGpt extends GptBase {
17
18
  protected readonly apiKey: string;
18
19
  private aiApi: OpenAIApi | undefined;
19
20
  protected readonly chatModel: string;
@@ -34,8 +35,8 @@ export default class OpenAIGpt extends EventEmitter {
34
35
  /**
35
36
  * 初始化OpenAI 的聊天对象Api
36
37
  */
37
- createOpenAI(apiKey:string):OpenAIApi {
38
- return new OpenAIApi(new Configuration({ apiKey }))
38
+ createOpenAI(apiKey: string): OpenAIApi {
39
+ return new OpenAIApi(new Configuration({ apiKey }))
39
40
  }
40
41
  /**
41
42
  * 向OpenAI发送一个聊天请求
@@ -43,22 +44,22 @@ export default class OpenAIGpt extends EventEmitter {
43
44
  */
44
45
  public async chatRequest(chatText: string | Array<any>, callChatOption: OpenAIApiParameters, axiosOption: any = {}): Promise<ChatReponse> {
45
46
  if (!chatText) return { successed: false, error: { errcode: 2, errmsg: '缺失聊天的内容' } };
46
- if (!this.aiApi){
47
+ if (!this.aiApi) {
47
48
  this.aiApi = this.createOpenAI(this.apiKey);
48
49
  //return { successed: false, error: { errcode: 1, errmsg: '聊天机器人无效' } };
49
- }
50
+ }
50
51
 
51
52
  let message: Array<ChatCompletionRequestMessage> = typeof (chatText) == 'string' ?
52
53
  [{ role: 'user', content: chatText }] : chatText;
53
54
  // console.log('message', message)
54
55
  try {
55
- const response= await this.aiApi.createChatCompletion({
56
- model: callChatOption?.model || this.chatModel,
57
- messages: message,
58
- temperature: Number(callChatOption?.temperature || this.temperature),
59
- max_tokens: Number(callChatOption?.maxtoken || this.maxtoken),
60
- n: Number(callChatOption?.replyCounts || 1) || 1
61
- }, axiosOption);
56
+ const response = await this.aiApi.createChatCompletion({
57
+ model: callChatOption?.model || this.chatModel,
58
+ messages: message,
59
+ temperature: Number(callChatOption?.temperature || this.temperature),
60
+ max_tokens: Number(callChatOption?.maxtoken || this.maxtoken),
61
+ n: Number(callChatOption?.replyCounts || 1) || 1
62
+ }, axiosOption);
62
63
  return { successed: true, message: response.data.choices };
63
64
  } catch (error) {
64
65
  console.log('result is error ', error)
@@ -67,7 +68,36 @@ export default class OpenAIGpt extends EventEmitter {
67
68
 
68
69
  }
69
70
 
70
-
71
+ /**
72
+ * 点评问题回答的评价
73
+ * @param question
74
+ * @param answer
75
+ * @param axiosOption
76
+ */
77
+ async commentQuestionAnswer(question: string, answer: string, axiosOption: any = { timeout: 30000 }): Promise<CommentResult>{
78
+ if (!question || !answer) return { successed: false, error: { errcode: 2, errmsg: '缺失参数' } }
79
+ let message = [
80
+ {role:'system',content:'你是一名专业的培训师。'},
81
+ { role: 'user', content: `问题题干:“${question}”`},
82
+ { role: 'user', content: `回答内容:“${answer}”` },
83
+ { role: 'user', content: `请根据以上的回答内容进行点评,给出一段不超过200字的评语,以及0-100的得分。最终结果按照{"comment":"评语","score":80}的JSON结构输出` }
84
+ ]
85
+ const result = await this.chatRequest(message, {}, axiosOption);
86
+ if (result.successed && result.message) {
87
+ let value = result.message[0].message.content.trim();
88
+ let replyJson = this.fixedJsonString(value);
89
+ ///能够提取到内容
90
+ if (replyJson.length)return { successed: true,...replyJson[0] }
91
+ ///回答的内容非JSON格式,自己来提取算了
92
+ console.log('自己组装')
93
+ let matched = value.match(/\d+分/g),score=0;
94
+ if (matched && matched.length){
95
+ score =Number(matched[0].replace('分',''));
96
+ }
97
+ return {successed:true,comment:value,score}
98
+ }
99
+ return { successed: false};
100
+ }
71
101
  /**
72
102
  * 判断一句话的表达情绪
73
103
  * @param {*} s1
@@ -171,7 +201,7 @@ export default class OpenAIGpt extends EventEmitter {
171
201
  ///没20句话分为一组,适应大文件内容多次请求组合结果
172
202
  ///每一句话需要产生的题目
173
203
  let questions4EverySentense: number = count / arrContent.length; //Math.ceil(arrContent.length / 20);
174
- let faqs: Array<FaqItem> = [], gotted: number = 0;
204
+ let faqs: FaqItem[] = [], gotted: number = 0;
175
205
  while (arrContent.length > 0 && gotted < count) {
176
206
  questions4EverySentense = (count - gotted) / arrContent.length
177
207
  ////每次最多送MESSAGE_LENGTH句话给openai
@@ -180,7 +210,7 @@ export default class OpenAIGpt extends EventEmitter {
180
210
  //subarray.push({ role: 'user', content:'请根据上述内容,给出一道提问与答案以及答案关键词,按照先问题内容,再标准答案,再关键词的顺序输出,关键词之间用、分开'})
181
211
  subarray.unshift({ role: 'system', content: `你是一位专业培训师,从以下内容中提取${itemCount}条提问及答案,并从答案内容提取出至少2个关键词,最终结果按照[{"question":"提问内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]的JSON数组结构输出。` })
182
212
  //subarray.unshift({ role: 'system', content: `你是一位专业程序开发工程师,根据以下内容,按照[{"question":"问题内容","answer":"答案内容","keywords":["关键词1","关键词2"]}]JSON数组结构,给出${itemCount}条提问问题及答案以及答案关键词` })
183
- console.log('subarray', subarray)
213
+ // console.log('subarray', subarray)
184
214
  let result = await this.chatRequest(subarray, { replyCounts: 1 }, axiosOption);
185
215
  if (result.successed && result.message) {
186
216
  // console.log('result is ', result.message[0].message.content)
@@ -200,7 +230,7 @@ export default class OpenAIGpt extends EventEmitter {
200
230
  arrContent = []; /// 释放内存
201
231
  ///发出信号,解析完毕
202
232
  this.emit('parseover', { type: 'qa', items: faqs })
203
- return { successed: true, message: faqs };
233
+ return { successed: true, message: faqs.slice(0, count) };
204
234
  }
205
235
 
206
236
  /**
@@ -240,16 +270,18 @@ export default class OpenAIGpt extends EventEmitter {
240
270
  */
241
271
  protected pickUpFaqContent(messages: Array<any>): Array<FaqItem> {
242
272
  let answerString = messages[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
243
- const firstsybmol = answerString.indexOf("["); ////必须过滤出来数组
244
- const lastsybmol = answerString.lastIndexOf("]");
273
+ // const firstsybmol = answerString.indexOf("["); ////必须过滤出来数组
274
+ // const lastsybmol = answerString.lastIndexOf("]");
245
275
 
246
- console.log('answerString', answerString)
247
- if (firstsybmol < 0 || lastsybmol < 0 || lastsybmol <= firstsybmol) return [];
248
- answerString = answerString.substr(firstsybmol, lastsybmol - firstsybmol + 1);
249
- console.log('answerString', answerString)
276
+ // console.log('answerString', answerString)
277
+ // if (firstsybmol < 0 || lastsybmol < 0 || lastsybmol <= firstsybmol) return [];
278
+ // answerString = answerString.substr(firstsybmol, lastsybmol - firstsybmol + 1);
279
+ // console.log('answerString', answerString)
280
+ let jsonObj = this.fixedJsonString(answerString);
281
+ if (!jsonObj.length) return []
250
282
  try {
251
283
  //let jsonObj = JSON.parse(answerString);
252
- let jsonObj = eval(answerString);
284
+ //let jsonObj = eval(answerString);
253
285
  jsonObj.map((item: FaqItem) => {
254
286
  let realKeyword: string[] = [];
255
287
  let keywords: string[] = (item.keywords + '').split(',');
@@ -348,29 +380,32 @@ export default class OpenAIGpt extends EventEmitter {
348
380
  */
349
381
  protected pickUpQuestions(result: Array<any>, count: number, questiontype: string, score: number = 1): Array<QuestionItem> {
350
382
  let answerString = result[0].message.content.trim().replace(/\t|\n|\v|\r|\f/g, '');
351
- const firstsybmol = answerString.indexOf("["); ////必须过滤出来数组
352
- const lastsybmol = answerString.lastIndexOf("]");
383
+ // const firstsybmol = answerString.indexOf("["); ////必须过滤出来数组
384
+ // const lastsybmol = answerString.lastIndexOf("]");
353
385
 
354
- console.log('answerString', answerString)
355
- if (firstsybmol < 0 || lastsybmol < 0 || lastsybmol <= firstsybmol) return [];
356
- answerString = answerString.substr(firstsybmol, lastsybmol - firstsybmol + 1);
357
- console.log('answerString', answerString)
386
+ // console.log('answerString', answerString)
387
+ // if (firstsybmol < 0 || lastsybmol < 0 || lastsybmol <= firstsybmol) return [];
388
+ // answerString = answerString.substr(firstsybmol, lastsybmol - firstsybmol + 1);
389
+ // console.log('answerString', answerString)
390
+ let jsonObj = this.fixedJsonString(answerString);
391
+ if (!jsonObj.length) return []
358
392
  let returnItems: QuestionItem[] = [];
359
393
  try {
360
- let jsonObj = JSON.parse(answerString);
361
- returnItems = jsonObj.map((questionitem: QuestionItem) => {
362
- console.log('answer item', questionitem);
394
+ // let jsonObj = JSON.parse(answerString);
395
+ returnItems = jsonObj.map((questionitem: any) => {
396
+ console.log('answer item from jsonObj', questionitem);
363
397
  if (questionitem.choice && Array.isArray(questionitem.choice) && questiontype != 'completion') {
364
398
  questionitem.fullanswer = (questionitem.answer + '').replace(/,|[^ABCDE]/g, '');
365
399
  questionitem.score = score;
366
400
  if (questionitem.choice) {
367
401
  questionitem.choice = questionitem.choice.map((item: string, index: number) => {
368
- let seqNo = String.fromCharCode(65 + index);
402
+ let seqNo = 'ABCDEFG'[index]; //String.fromCharCode(65 + index);
369
403
  let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig')
404
+ // console.log('itemitemitem', item)
370
405
  //let answer = jsonObj.fullanswer
371
406
  return {
372
407
  id: seqNo,
373
- content: item.replace(correctReg, '').trim(),
408
+ content: (item + '').replace(correctReg, '').trim(),
374
409
  iscorrect: (questionitem.fullanswer || '').indexOf(seqNo) >= 0 ? 1 : 0
375
410
  //|| jsonObj.fullanswer.indexOf(m))
376
411
  }
@@ -396,7 +431,7 @@ export default class OpenAIGpt extends EventEmitter {
396
431
  }
397
432
  ///单选题验证
398
433
  if (questiontype == 'singlechoice') {
399
- let rightAnswer = questionitem.choice ? questionitem.choice.filter(item => { return item.iscorrect === 1 }) : [];
434
+ let rightAnswer = questionitem.choice ? questionitem.choice.filter((item: { iscorrect: number; }) => { return item.iscorrect === 1 }) : [];
400
435
  ///单选题的正确选项大于了1个
401
436
  if (rightAnswer.length != 1 || !questionitem.answer || questionitem.answer.length !== 1) return null;
402
437
  ///正确选项和答案不一致
@@ -404,7 +439,7 @@ export default class OpenAIGpt extends EventEmitter {
404
439
  }
405
440
  ///多选题验证
406
441
  if (questiontype == 'multiplechoice') {
407
- let rightAnswer = questionitem.choice ? questionitem.choice.filter(item => { return item.iscorrect === 1 }) : [];
442
+ let rightAnswer = questionitem.choice ? questionitem.choice.filter((item: { iscorrect: number; }) => { return item.iscorrect === 1 }) : [];
408
443
  ///单选题的正确选项大于了1个
409
444
  if (rightAnswer.length === 0 || !questionitem.answer || questionitem.answer.length === 0) return null;
410
445
  }
@@ -415,43 +450,54 @@ export default class OpenAIGpt extends EventEmitter {
415
450
  console.log('error happened:', err);
416
451
  }
417
452
  return returnItems.filter(i => { return i != null; }).slice(0, count);
418
- // let item = result.map(m => {
419
- // ////防止输出的JSON格式不合法
420
- // try {
421
- // let jsonObj = JSON.parse(m.message.content)
422
- // jsonObj.score = score;
423
- // if (jsonObj.choice && Array.isArray(jsonObj.choice) && questiontype != 'completion') {
424
- // jsonObj.fullanswer = (jsonObj.answer + '').replace(/,|[^ABCDE]/g, '');
425
- // jsonObj.choice = jsonObj.choice.map((item:string, index:number) => {
426
- // let seqNo = String.fromCharCode(65 + index);
427
- // let correctReg = new RegExp(`${seqNo}.|${seqNo}`, 'ig')
428
- // //let answer = jsonObj.fullanswer
429
- // return {
430
- // id: seqNo,
431
- // content: item.replace(correctReg, '').trim(),
432
- // iscorrect: (jsonObj.fullanswer.indexOf(seqNo) >= 0 || jsonObj.fullanswer.indexOf(m)) >= 0 ? 1 : 0
433
- // }
434
- // })
435
- // }
436
- // switch (questiontype) {
437
- // case 'singlechoice':
438
- // jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('').slice(0, 1);
439
- // break;
440
- // case 'multiplechoice':
441
- // jsonObj.answer = (jsonObj.answer + '').replace(/,|[^ABCDEFG]/g, '').split('');
442
- // break;
443
- // case 'trueorfalse':
444
- // jsonObj.answer = [(jsonObj.answer + '').indexOf('正确') >= 0 ? 'A' : 'B']
445
- // break;
446
- // }
447
- // return jsonObj;
448
- // } catch (err) {
449
- // console.log('error happened:', err);
450
- // return null;
451
- // }
452
- // })
453
- // return item.filter(i => { return i != null; });
454
453
  }
454
+ /**
455
+ * 验证JSON字符串是否是真正可转换为JSON的合法格式
456
+ * 这里只能做一个最简单的处理,就是用两端的符号
457
+ * @param jsonstr
458
+ */
459
+ protected fixedJsonString(jsonstr: string): any[] {
460
+ console.log('original json string:', jsonstr)
461
+ ///检查返回的是不是一个数组对象(我们需要的是数组对象)
462
+ let firstBracketSymbol = jsonstr.indexOf("["); ////必须过滤出来数组
463
+ let lastBracketSymbol = jsonstr.lastIndexOf("]");
464
+ ///第一个花括号出现的位置,如果花括号出现的位置早于 [ ,则默认返回的对象不是一个数组,仅仅是一个对象,
465
+ ///则需要我们用中括号包住
466
+ let firstBraceSymbol = jsonstr.indexOf("{");
467
+ let lastBraceSymbol = jsonstr.lastIndexOf("}");
468
+ ///返回的不是一个数组结构的,只是一个{},我们帮他完成数组拼接
469
+ if (firstBraceSymbol >= 0 &&
470
+ firstBraceSymbol < (firstBracketSymbol >= 0 ? firstBracketSymbol:1000) &&
471
+ lastBraceSymbol > firstBraceSymbol &&
472
+ lastBraceSymbol >= 0 && lastBraceSymbol > lastBracketSymbol){
473
+
474
+ jsonstr = '[' + jsonstr.substr(firstBraceSymbol, lastBraceSymbol - firstBraceSymbol + 1); +']';
475
+ firstBracketSymbol = 0;
476
+ lastBracketSymbol = jsonstr.length-1;
477
+ }
478
+ else if (firstBracketSymbol < 0 || lastBracketSymbol < 0 || lastBracketSymbol <= firstBracketSymbol){
479
+ return [];
480
+ }
481
+ jsonstr = jsonstr.substr(firstBracketSymbol, lastBracketSymbol - firstBracketSymbol + 1);
482
+ ///尽量处理一些能够一眼识别出来的JSON错误
483
+ jsonstr = jsonstr.replace(/}{/g,'},{');
484
+ let mutilitems = jsonstr.split('][');
485
+ ///确实存在多个数组拼接在一起,中间没有逗号隔开的了
486
+ let retObject:any[] = [];
487
+ for(let str of mutilitems){
488
+ if (!str.startsWith('[')) str = '[' + str;
489
+ if (!str.endsWith(']')) str = str+']';
490
+ // console.log('json str', str)
491
+ try{
492
+ let jsonObj = eval(str);
493
+ retObject = retObject.concat(jsonObj);
494
+ }catch(err){
495
+ console.log('json error', str)
496
+ }
497
+ }
498
+ return retObject;
499
+ }
500
+
455
501
  /**
456
502
  * 将一段很长的文本,按1024长度来划分到多个中
457
503
  * @param {*} content
@@ -474,4 +520,5 @@ export default class OpenAIGpt extends EventEmitter {
474
520
  }
475
521
  return message;
476
522
  }
523
+
477
524
  }
@@ -1,32 +0,0 @@
1
- /**
2
- * 语音转文字服务商工厂
3
- */
4
- import OpenAIGpt from './openai';
5
- import AzureAI from './azureai'
6
- /**
7
- * OpenAI/NLP 的服务提供商 OpenAI,微软,百度文心(待接入),google(待接入)
8
- */
9
- export const AIProviderEnum = {
10
- OPENAI: 'openai',
11
- MICROSOFT: 'microsoft',
12
- BAIDU: 'baidu',
13
- GOOGLE:'google'
14
- } as const;
15
- export type AIProviderEnum = typeof AIProviderEnum[keyof typeof AIProviderEnum];
16
- /**
17
- * 根据类型创建不同的TTS引擎对象
18
- * @param {*} provider
19
- * @param {*} apikey
20
- * @param {*} setting
21
- * @returns
22
- */
23
- export function createAIInstance(provider: AIProviderEnum, apikey: string, setting: any): OpenAIGpt | null {
24
- let { model, maxtoken, temperature,endpoint,engine,version } = setting;
25
- switch (provider) {
26
- case AIProviderEnum.OPENAI:
27
- return new OpenAIGpt(apikey, { model, maxtoken, temperature });
28
- case AIProviderEnum.MICROSOFT:
29
- return new AzureAI(apikey, { endpoint, engine, version }, { model, maxtoken, temperature }, );
30
- default: return null;
31
- }
32
- };