article_annotation.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # -*- coding: utf-8 -*-
  2. """
  3. 1.对拿到的文章或句子,进行单词的切割;考虑,如何是大文本应该进行小段切割,防止巨量文本的传入;遇到巨量文本,切割后分多个问题,调用正常的运行流程;把custom_id的后缀加上123456标记
  4. 2.判断文章是否有缩写;将缩写还原,缩写标注第一个单词;例如it’s 标注it的词义id; 缩写获得的形式,写入mysql的缩写表
  5. 3.获取每个单词的词义数据包;
  6. 20250522
  7. 文章标注接口,分为2个接口,传文本同步返回,传文章对象异步返回。
  8. 下面这些情况,暂时不标注;1.缩写 2.连字符 3.春笋词义表内没有数据
  9. 注意:传入的文本将使用空格切分,连在一起的字符会被当做一个单词处理;
  10. """
  11. import json
  12. from random import randint
  13. from cachetools import TTLCache
  14. from openpyxl import load_workbook
  15. from core.respone_format import *
  16. from data.get_all_exchange_words import word_to_prototype
  17. from gpt.chatgpt import get_annotation_gpt_pydantic
  18. from tools.loglog import log_err_e, logger
  19. from tools.thread_pool_manager import pool_executor
  20. class Annotation:
  21. def __init__(self):
  22. self.all_task_data: dict[int, list] = {}
  23. self.all_task_result = TTLCache(maxsize=1000, ttl=3600)
  24. self.word_meaning_dict: dict[str, list[tuple[int, str, str]]] = {}
  25. self.prototype_words = set()
  26. self.change_prototype_dict = {}
  27. self.get_excel_meaning_data()
  28. self.get_excel_change_data()
  29. def submit_task(self, english_text, real_ip):
  30. task_id = randint(10000000, 99999999)
  31. logger.info(f"/article/annotation 生成id。task_id:{task_id},real_ip:{real_ip}")
  32. f = pool_executor.submit(self.main_annotation, task_id, english_text)
  33. r = f.result()
  34. return r
  35. def __run(self):
  36. for task_id, task_data in self.all_task_data.items():
  37. english_text, = task_data
  38. self.main_annotation(task_id, english_text)
  39. def main_annotation(self, task_id: int, english_text: str):
  40. split_words = english_text.split()
  41. meanings_data = self.query_meanings_data(split_words=split_words)
  42. result_annotation = self.__ai_annotation(english_text=english_text, meanings_data=meanings_data)
  43. self.all_task_result[task_id] = result_annotation
  44. return result_annotation
  45. async def query_result_by_taskid(self, task_id):
  46. if task_id in self.all_task_result:
  47. r = self.all_task_result[task_id]
  48. return resp_200(data=r)
  49. return resp_200(data={})
  50. def get_excel_meaning_data(self):
  51. """读取外部的春笋词义表,结构化到字典;单词为键,值[((词义id,中文词义))]"""
  52. spring_bamboo_meaning_path = "data/春笋词义表.xlsx"
  53. wb = load_workbook(spring_bamboo_meaning_path, read_only=True, data_only=True)
  54. ws = wb.active
  55. try:
  56. for index, row in enumerate(ws.values, start=1):
  57. if index == 1:
  58. continue
  59. word = row[3]
  60. id_and_meaning = (row[0], word, row[2])
  61. if word not in self.word_meaning_dict:
  62. self.word_meaning_dict[word] = [id_and_meaning]
  63. else:
  64. self.word_meaning_dict[word].append(id_and_meaning)
  65. except Exception as e:
  66. log_err_e(e, msg="打开春笋词义表错误")
  67. finally:
  68. wb.close()
  69. def get_excel_change_data(self):
  70. """读取外部的春笋变形表"""
  71. spring_bamboo_change_path = "data/春笋单词对照变形.xlsx"
  72. wb = load_workbook(spring_bamboo_change_path, read_only=True, data_only=True)
  73. ws = wb.active
  74. try:
  75. for row in ws.values:
  76. word_prototype = row[0]
  77. word_change = row[1]
  78. self.prototype_words.add(word_prototype)
  79. self.change_prototype_dict[word_change] = word_prototype
  80. except Exception as e:
  81. log_err_e(e, msg="打开春笋变形表错误")
  82. finally:
  83. wb.close()
  84. def to_prototype_word(self, word):
  85. if word in self.prototype_words:
  86. w_prototype = word
  87. elif word.lower() in self.prototype_words:
  88. w_prototype = word.lower()
  89. elif word in self.change_prototype_dict:
  90. w_prototype = self.change_prototype_dict[word]
  91. else:
  92. w_prototype = word_to_prototype(word)
  93. return w_prototype
  94. def __query_meaning(self, word: str) -> str:
  95. """
  96. :param word: 单个单词
  97. :return: 加工好的词义文本
  98. """
  99. meaning_data1 = []
  100. if word in self.word_meaning_dict:
  101. meaning_data1.extend(self.word_meaning_dict[word])
  102. meaning_data_str = "".join([f"[{i[0]} {i[1]} {i[2]}]" for i in meaning_data1])
  103. return meaning_data_str
  104. elif word.lower() in self.word_meaning_dict:
  105. meaning_data1.extend(self.word_meaning_dict[word.lower()])
  106. meaning_data_str = "".join([f"[{i[0]} {i[1]} {i[2]}]" for i in meaning_data1])
  107. return meaning_data_str
  108. w_prototype = self.to_prototype_word(word)
  109. key_to_check = w_prototype if w_prototype in self.word_meaning_dict else w_prototype.lower()
  110. if key_to_check in self.word_meaning_dict:
  111. meaning_data = self.word_meaning_dict[key_to_check]
  112. meaning_data1.extend(meaning_data)
  113. meaning_data1 = list(set(meaning_data1))
  114. meaning_data_str = "".join([f"[{i[0]} {i[1]} {i[2]}]" for i in meaning_data1])
  115. return meaning_data_str
  116. def query_meanings_data(self, split_words: list):
  117. """
  118. 查询所有单词的词义数据包
  119. :param split_words: 文章或句子被切割后的单词列表,连字符也拆开
  120. :return:
  121. """
  122. all_words_meaning_list = set()
  123. for word in split_words:
  124. result_query_meaning: str = self.__query_meaning(word)
  125. if result_query_meaning:
  126. all_words_meaning_list.add(f"【{word} {result_query_meaning}】")
  127. new_data_str = "\n词义数据包:\n" + "\n".join(all_words_meaning_list) + "\n\n"
  128. return new_data_str
  129. @staticmethod
  130. def __parse_gpt_resp(gpt_resp: dict):
  131. """
  132. 解析ai-gpt的回复
  133. :param gpt_resp: GPT原始的回复
  134. :return:
  135. """
  136. r = json.loads(gpt_resp["choices"][0]["message"]["content"])
  137. return r
  138. def __ai_annotation(self, english_text, meanings_data):
  139. """
  140. AI词义标注
  141. :param english_text: 英语文本
  142. :param meanings_data: 词义数据包
  143. :return:
  144. """
  145. sys_question = """你是一个英语文本的词义标注师,工作是按要求对句子或文章进行词义id的标注。下面我将提供一篇英语文本以及一个包含单词ID和词义的数据包。
  146. 你的工作是对英语文本中的每个单词的原型,根据提供的词义数据包选择这个单词原型最合适的词义,并在单词后附上对应的词义ID。标注格式为:word[word_id]。
  147. 要求:
  148. 1.如果词义数据包中没有该单词或找不到合适的词义,请标注该单词在文中词义的中文翻译。示例:seismography[地震学] car[猫]。
  149. 2.如果是[连字符-、中文、标点符号、数字、百分比、序号A.B.C.D.或者日期],这些不是英语单词,不用标记,保持原样不变。示例`1999 2025 18:00 苹果 ____ A. B. C. D. e-mail Exhaust-fans`,这些都不标记。
  150. 3.标注每个英语单词,不是短语。错误示例:be good at[擅长]。正确示例:be[11] good[12] at[13]。
  151. 4.如果没有提供词义,则不标注。
  152. 回复格式要求如下:
  153. - 请按照用户原文顺序和格式返回处理后的文本。空格和换行\\n,不用改变,不要加减空格,与原文一致。
  154. - 每个单词后面标注上其对应的词义ID,格式为:`word[word_id]`。
  155. 最终回复示例:If[1] a[2] dog[3] causes[4] a[5] cat[6] accident[7] and[8] gets[9] killed[10]
  156. 请确保理解上述说明并准备好接收英语文本及词义数据包。"""
  157. user_question = "英语文本:\n" + english_text + meanings_data
  158. gpt_resp = get_annotation_gpt_pydantic(question=user_question, sys_prompt=sys_question, max_tokens=8000)
  159. result_annotation = self.__parse_gpt_resp(gpt_resp=gpt_resp)
  160. return result_annotation