-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathentity_extractor.py
More file actions
305 lines (258 loc) · 9.72 KB
/
Copy pathentity_extractor.py
File metadata and controls
305 lines (258 loc) · 9.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
"""
实体抽取模块
支持三种抽取方式:
1. 规则匹配(正则 + 词典)
2. LLM 抽取(调用大模型)
3. jieba 分词 + 词性标注
"""
import re
from typing import Optional
from models import Entity, EntityType
# ============================================================
# 实体词典(可扩展)
# ============================================================
# 常见人名后缀
PERSON_SUFFIXES = ["先生", "女士", "教授", "博士", "总", "经理", "主任", "院长", "校长"]
# 常见组织后缀
ORG_SUFFIXES = ["公司", "集团", "大学", "学院", "研究所", "研究院", "基金会", "协会", "委员会", "局", "部", "院"]
# 常见地点后缀
LOC_SUFFIXES = ["省", "市", "区", "县", "镇", "村", "路", "街", "大道", "广场"]
# 技术关键词
TECH_KEYWORDS = [
"Python", "Java", "JavaScript", "TypeScript", "Go", "Rust", "C++",
"React", "Vue", "Angular", "Django", "FastAPI", "Flask", "Spring",
"Docker", "Kubernetes", "K8s", "Redis", "MySQL", "PostgreSQL", "MongoDB",
"TensorFlow", "PyTorch", "Keras", "Hugging Face", "OpenAI", "GPT",
"Linux", "Nginx", "Apache", "Git", "GitHub", "GitLab",
"机器学习", "深度学习", "自然语言处理", "NLP", "计算机视觉", "CV",
"知识图谱", "图数据库", "Neo4j", "向量数据库", "RAG",
"微服务", "容器化", "DevOps", "CI/CD", "云原生",
]
class EntityExtractor:
"""
实体抽取器
使用规则 + jieba 分词进行实体识别
"""
def __init__(self):
"""初始化抽取器"""
# 尝试加载 jieba
try:
import jieba
import jieba.posseg as pseg
self.jieba = jieba
self.pseg = pseg
self.has_jieba = True
except ImportError:
self.has_jieba = False
# 编译正则模式
self._compile_patterns()
def _compile_patterns(self):
"""编译正则表达式模式"""
self.patterns = {
# 日期
"date": re.compile(
r'\d{4}[-/年]\d{1,2}[-/月]\d{1,2}[日]?'
r'|\d{4}年\d{1,2}月'
),
# 邮箱
"email": re.compile(
r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
),
# URL
"url": re.compile(
r'https?://[^\s<>"{}|\\^`\[\]]+'
),
# 电话
"phone": re.compile(
r'1[3-9]\d{9}'
r'|\d{3,4}-\d{7,8}'
),
# 百分比
"percent": re.compile(
r'\d+\.?\d*%'
),
# 金额
"money": re.compile(
r'[\$¥€£]\d+\.?\d*[亿万]?'
r'|\d+\.?\d*[亿万]?元'
r'|\d+\.?\d*[亿万]?美元'
),
# 版本号
"version": re.compile(
r'v?\d+\.\d+(\.\d+)?'
),
}
def extract(self, text: str, doc_id: str = "") -> list[Entity]:
"""
从文本中抽取实体
参数:
text: 输入文本
doc_id: 文档 ID
返回:
实体列表
"""
entities = []
seen = set()
# 1. 正则抽取(日期、邮箱、URL 等)
regex_entities = self._extract_by_regex(text)
for entity in regex_entities:
if entity.name not in seen:
entities.append(entity)
seen.add(entity.name)
# 2. jieba 分词抽取(人名、地名、机构名)
if self.has_jieba:
jieba_entities = self._extract_by_jieba(text)
for entity in jieba_entities:
if entity.name not in seen:
entity.source_doc_ids = [doc_id] if doc_id else []
entities.append(entity)
seen.add(entity.name)
# 3. 技术关键词抽取
tech_entities = self._extract_technologies(text)
for entity in tech_entities:
if entity.name not in seen:
entity.source_doc_ids = [doc_id] if doc_id else []
entities.append(entity)
seen.add(entity.name)
# 4. 基于规则的实体抽取(后缀匹配)
rule_entities = self._extract_by_rules(text)
for entity in rule_entities:
if entity.name not in seen:
entity.source_doc_ids = [doc_id] if doc_id else []
entities.append(entity)
seen.add(entity.name)
return entities
def _extract_by_regex(self, text: str) -> list[Entity]:
"""使用正则表达式抽取实体"""
entities = []
for pattern_name, pattern in self.patterns.items():
matches = pattern.findall(text)
for match in matches:
# 根据模式确定实体类型
if pattern_name == "date":
entity_type = EntityType.OTHER
elif pattern_name == "email":
entity_type = EntityType.OTHER
elif pattern_name == "url":
entity_type = EntityType.OTHER
elif pattern_name == "phone":
entity_type = EntityType.OTHER
elif pattern_name == "money":
entity_type = EntityType.OTHER
else:
entity_type = EntityType.OTHER
entities.append(Entity(
name=match,
entity_type=entity_type,
properties={"source": "regex", "pattern": pattern_name},
confidence=0.8,
))
return entities
def _extract_by_jieba(self, text: str) -> list[Entity]:
"""使用 jieba 分词抽取实体"""
entities = []
words = self.pseg.cut(text)
for word, flag in words:
if len(word) < 2:
continue
entity_type = None
confidence = 0.7
# nr: 人名
if flag == "nr":
entity_type = EntityType.PERSON
confidence = 0.8
# ns: 地名
elif flag == "ns":
entity_type = EntityType.LOCATION
confidence = 0.8
# nt: 机构名
elif flag == "nt":
entity_type = EntityType.ORGANIZATION
confidence = 0.8
# nz: 其他专名
elif flag == "nz":
entity_type = EntityType.CONCEPT
confidence = 0.6
if entity_type:
entities.append(Entity(
name=word,
entity_type=entity_type,
properties={"source": "jieba", "pos": flag},
confidence=confidence,
))
return entities
def _extract_technologies(self, text: str) -> list[Entity]:
"""抽取技术关键词"""
entities = []
for tech in TECH_KEYWORDS:
# 不区分大小写匹配
if re.search(re.escape(tech), text, re.IGNORECASE):
entities.append(Entity(
name=tech,
entity_type=EntityType.TECHNOLOGY,
properties={"source": "keyword"},
confidence=0.9,
))
return entities
def _extract_by_rules(self, text: str) -> list[Entity]:
"""
基于规则抽取实体
使用已知实体词典,避免正则误匹配
"""
entities = []
seen = set()
# 已知组织名称(可扩展)
known_orgs = {
"阿里巴巴", "腾讯", "百度", "字节跳动", "华为", "小米",
"京东", "美团", "网易", "拼多多", "滴滴", "快手",
"北京大学", "清华大学", "浙江大学", "复旦大学",
"中国科学院", "中国工程院", "OpenAI", "Google", "Microsoft",
}
# 已知地点(可扩展)
known_locs = {
"北京", "上海", "广州", "深圳", "杭州", "南京",
"成都", "武汉", "西安", "重庆", "天津", "苏州",
"硅谷", "纽约", "伦敦", "东京",
}
# 检查已知组织
for org in known_orgs:
if org in text and org not in seen:
entities.append(Entity(
name=org,
entity_type=EntityType.ORGANIZATION,
properties={"source": "known"},
confidence=0.95,
))
seen.add(org)
# 检查已知地点
for loc in known_locs:
if loc in text and loc not in seen:
entities.append(Entity(
name=loc,
entity_type=EntityType.LOCATION,
properties={"source": "known"},
confidence=0.95,
))
seen.add(loc)
return entities
def extract_from_llm(self, text: str, api_key: str = None) -> list[Entity]:
"""
使用 LLM 抽取实体(需要 API Key)
这是一个预留接口,实际使用时需要配置 API Key
"""
# TODO: 实现 LLM 抽取
# prompt = f"""
# 从以下文本中抽取实体,返回 JSON 格式:
# {text}
#
# 返回格式:
# [{{"name": "实体名", "type": "实体类型", "description": "描述"}}]
# """
pass
# ============================================================
# 快捷函数
# ============================================================
def extract_entities(text: str, doc_id: str = "") -> list[Entity]:
"""抽取实体的快捷函数"""
extractor = EntityExtractor()
return extractor.extract(text, doc_id)