article_annotation.py 9.1 KB

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