Skip to content

Commit b0845f7

Browse files
committed
[bugfix][hop_infos speed up]
1 parent 206cac2 commit b0845f7

File tree

4 files changed

+193
-36
lines changed

4 files changed

+193
-36
lines changed

muagent/base_configs/prompts/simple_prompts.py

Lines changed: 172 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -207,45 +207,83 @@
207207

208208

209209

210-
text2EKG_prompt_en = '''你是一个结构化信息抽取的专家,你需要根据输入的文档,抽取其中的关键节点及节点间的连接顺序。请用json结构返回。
211-
212-
json结构定义如下:
210+
text2EKG_prompt_en = '''# 上下文 #
211+
给定一个关于某个描述流程或者步骤的输入文本,我需要根据给定的输入文本,得到输入文本中流程或者操作步骤的结构化表示,之后可以用来在其它程序中绘制流程图。
212+
你是一个结构化信息抽取和总结的专家,你可以根据输入的流程相关描述文本,抽取其中的关键节点及节点间的连接顺序,生成流程或者步骤的结构化json表示。
213+
#############
214+
# 目标 #
215+
我希望你根据输入文本,提供一个输入文本中流程、操作的结构化json表示。可以参考以下步骤思考,但是不要输出每个步骤中间结果,只输出最后的流程图json:
216+
1. 确定流程图节点: 根据输入文本内容,确定流程图的各个节点。节点可以用如下结构表示:
213217
{
214218
"nodes": {
215219
"节点序号": {
216220
"type": "节点类型",
217221
"content": "节点内容"
218-
}
219-
},
220-
"edges": [
221-
{
222-
"start": "起始节点序号",
223-
"end": "终止节点序号"
224-
}
225-
]
222+
},
223+
}
226224
}
227-
其中 nodes 用来存放抽取的节点,每个 node 的 key 通过从0开始对递增序列表示,value 是一个字典,包含 type 和 content 两个属性, type 对应下面定义的三种节点类型,content 为抽取的节点内容。
228-
edges 用来存放节点间的连接顺序,它是一个列表,每个元素是一个字典,包含 start 和 end 两个属性, start 为起始 node 的 节点序号, end 为结束 node 的 节点序号。
229-
225+
其中 nodes 用来存放抽取的节点,每个 node 的 key 通过从0开始对递增序列表示,value 是一个字典,包含 type 和 content 两个属性, type 对应下面定义的四种节点类型,content 为抽取的节点内容。
230226
节点类型定义如下:
231227
Schedule:
232-
表示整篇输入文档所做的事情,是对整篇输入文档的总结
228+
表示输入文本中流程和操作要完成的事情和任务,是对输入文本的意图的总结
233229
第一个节点永远是Schedule节点。
234230
Task:
235-
表示需要执行的任务
231+
表示该节点需要执行的任务
236232
Phenomenon:
237233
表示依据Task节点的执行结果,得到的事实结论。
238234
Phenomenon节点只能连接在Task节点之后。
239235
Analysis:
240236
表示依据Phenomenon节点的事实进行推断的过程;
241237
Analysis节点只能连接在Phenomenon节点之后。
242238
243-
以下是一个例子:
244-
input: 路径:排查网络问题
239+
2. 连接流程图节点: 根据输入文本内容,确定流程图的各个节点的连接关系。节点之间的连接关系可以用如下结构表示:
240+
{
241+
"edges": [
242+
{
243+
"start": "起始节点序号",
244+
"end": "终止节点序号"
245+
}
246+
]
247+
}
248+
edges 用来存放节点间的连接顺序,它是一个列表,每个元素是一个字典,包含 start 和 end 两个属性, start 为起始 node 的 节点序号, end 为结束 node 的 节点序号。
249+
250+
3. 生成表示流程图的完整json: 将上面[确定流程图节点]和[连接流程图节点]步骤中的结果放到一个json,检查生成的流程图是否符合给定输入文本的内容,优化流程图的结构,合并相邻同类型节点,返回最终的json。
251+
#############
252+
# 风格 #
253+
流程图节点数尽可能少,保持流程图结构简洁,相邻同类型节点可以合并。流程图节点中的节点内容content要准确、详细、充分。
254+
#############
255+
# 语气 #
256+
专业,技术性
257+
#############
258+
# 受众 #
259+
面向提供流程文本的人员,让他们确信你生成的流程图准确表示了文本中的流程步骤。
260+
#############
261+
# 响应 #
262+
返回json结构定义如下:
263+
{
264+
"nodes": {
265+
"节点序号": {
266+
"type": "节点类型",
267+
"content": "节点内容"
268+
}
269+
},
270+
"edges": [
271+
{
272+
"start": "起始节点序号",
273+
"end": "终止节点序号"
274+
}
275+
]
276+
}
277+
#############
278+
# 例子 #
279+
以下是几个例子:
280+
281+
# 例子1 #
282+
输入文本:路径:排查网络问题
245283
1. 通过观察sofagw网关监控发现,BOLT失败数突增
246284
2. 且失败曲线与退保成功率曲线相关性较高,判定是网络问题。
247285
248-
output: {
286+
输出:{
249287
"nodes": {
250288
"0": {
251289
"type": "Schedule",
@@ -296,10 +334,122 @@
296334
]
297335
}
298336
299-
请根据上述说明和例子来对以下的输入文档抽取结构化信息:
337+
# 例子2 #
338+
输入文本:二、使用模版创建选品集
339+
STEP1:创建选品集
340+
注:因为只能选择同类型模版,必须先选择数据类型,才能选择模版创建
341+
STEP2:按需选择模版后,点击确认
342+
343+
- 我的收藏:个人选择收藏的模版
344+
- 我的创建:个人创建的模版
345+
- 模版广场:公开的模版,可以通过名称/创建人搜索到需要的模版并选择使用
346+
347+
STEP3:按需调整指标模版内的值,完成选品集创建
348+
349+
输出:{
350+
"nodes": {
351+
"0": {
352+
"type": "Schedule",
353+
"content": "使用模版创建选品集"
354+
},
355+
"1": {
356+
"type": "Task",
357+
"content": "创建选品集\n\n注:因为只能选择同类型模版,必须先选择数据类型,才能选择模版创建"
358+
},
359+
"2": {
360+
"type": "Task",
361+
"content": "按需选择模版后,点击确认\n\n - 我的收藏:个人选择收藏的模版 \n\n - 我的创建:个人创建的模版 \n\n - 模版广场:公开的模版,可以通过名称/创建人搜索到需要的模版并选择使用"
362+
},
363+
"3": {
364+
"type": "Task",
365+
"content": "按需调整指标模版内的值,完成选品集创建"
366+
}
367+
},
368+
"edges": [
369+
{
370+
"start": "0",
371+
"end": "1"
372+
},
373+
{
374+
"start": "1",
375+
"end": "2"
376+
},
377+
{
378+
"start": "2",
379+
"end": "3"
380+
}
381+
]
382+
}
383+
384+
# 例子3 #
385+
输入文本:Step1
386+
387+
- 点击右侧的左右切换箭头,找到自己所在的站点或业务模块;
388+
Step2
389+
390+
- 查询对应一级场景,若没有所需一级场景则联系 [@小明][@小红]添加:具体操作如下:
391+
- 邮件模板
392+
| 项目背景:<br />场景名称:<br />场景描述:<br />数据类型:商家/商品/营销商家/营销商品/权益商家/权益选品(必要的才选)<br />业务管理员:(花名) |
393+
| --- |
394+
395+
- 发送邮件[@小明][@小红]抄送 [@小白]
396+
Step3
397+
398+
- 查询对应二级场景,若没有所需二级场景则联系一级场景管理员添加,支持通过搜索二级场景名称和ID快速查询二级场景;
399+
- 一级管理员为下图蓝色框①所在位置查看
400+
Step4
401+
402+
- 申请二级场景数据权限,由对应二级场景管理员审批。若二级场景管理员为@小花@小映,按一级场景走申请流程。
403+
- 二级管理员为下图蓝色框②所在位置查看
404+
405+
输出:{
406+
"nodes": {
407+
"0": {
408+
"type": "Schedule",
409+
"content": "场景权限申请"
410+
},
411+
"1": {
412+
"type": "Task",
413+
"content": "点击右侧的左右切换箭头,找到自己所在的站点或业务模块"
414+
},
415+
"2": {
416+
"type": "Task",
417+
"content": "查询对应一级场景,若没有所需一级场景则联系 [@小明][@小红]添加:具体操作如下:\n 发送邮件给小明和小红,抄送小白,邮件内容包括项目背景,场景名称,场景描述,数据类型和业务管理员"
418+
},
419+
"3": {
420+
"type": "Task",
421+
"content": "查询对应二级场景,若没有所需二级场景则联系一级场景管理员添加,支持通过搜索二级场景名称和ID快速查询二级场景"
422+
},
423+
"4": {
424+
"type": "Task",
425+
"content": "申请二级场景数据权限,由对应二级场景管理员审批。若二级场景管理员为@小花@小映,按一级场景走申请流程"
426+
}
427+
},
428+
"edges": [
429+
{
430+
"start": "0",
431+
"end": "1"
432+
},
433+
{
434+
"start": "1",
435+
"end": "2"
436+
},
437+
{
438+
"start": "2",
439+
"end": "3"
440+
},
441+
{
442+
"start": "3",
443+
"end": "4"
444+
}
445+
]
446+
}
447+
#############
448+
# 开始抽取 #
449+
请根据上述说明和例子来对以下的输入文本抽取结构化流程json:
300450
301-
input: {text}
451+
输入文本:{text}
302452
303-
output:'''
453+
输出:'''
304454

305455
text2EKG_prompt_zh = text2EKG_prompt_en

muagent/db_handler/graph_db_handler/geabase_handler.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from muagent.db_handler.utils import deduplicate_dict
1414
from muagent.schemas.db import GBConfig
1515
from muagent.schemas.common import *
16-
from muagent.utils.common_utils import double_hashing
16+
from muagent.utils.common_utils import double_hashing, func_timer
1717

1818

1919
class GeaBaseHandler(GBHandler):
@@ -142,7 +142,6 @@ def get_current_nodeID(self, attributes: dict, node_type: str) -> int:
142142
def get_current_edgeID(self, src_id, dst_id, edeg_type:str = None):
143143
if not isinstance(src_id, int) or not isinstance(dst_id, int):
144144
result = self.get_current_edge(src_id, dst_id, edeg_type)
145-
logger.debug(f"{result}")
146145
return result.attributes.get("SRCID"), result.attributes.get("DSTID"), result.attributes.get("timestamp")
147146
else:
148147
return src_id, dst_id, 1
@@ -225,7 +224,7 @@ def get_hop_infos(self, attributes: dict, node_type: str = None, hop: int = 2, b
225224
'''
226225
hop >= 2, 表面需要至少两跳
227226
'''
228-
hop_max = 10
227+
hop_max = 8
229228
#
230229
where_str = ' and '.join([f"n0.{k}='{v}'" for k, v in attributes.items()])
231230
if reverse:
@@ -235,6 +234,7 @@ def get_hop_infos(self, attributes: dict, node_type: str = None, hop: int = 2, b
235234
last_node_ids, last_node_types = [], []
236235

237236
result = {}
237+
iter_index = 0
238238
while hop > 1:
239239
if last_node_ids == []:
240240
#
@@ -247,11 +247,13 @@ def get_hop_infos(self, attributes: dict, node_type: str = None, hop: int = 2, b
247247
#
248248
_result = self.execute(gql)
249249
_result = self.decode_result(_result, gql)
250+
# logger.info(f"p_lens: {len(_result['p'])}")
250251

251252
result = self.merge_hotinfos(result, _result)
252253
#
253-
last_node_ids, last_node_types, result = self.deduplicate_paths(result, block_attributes, select_attributes)
254+
last_node_ids, last_node_types, result = self.deduplicate_paths(result, block_attributes, select_attributes, hop=min(hop, hop_max)+iter_index*hop_max)
254255
hop -= hop_max
256+
iter_index += 1
255257

256258
nodes = self.convert2GNodes(result.get("n1", []))
257259
edges = self.convert2GEdges(result.get("e", []))
@@ -274,7 +276,7 @@ def get_hop_paths(self, attributes: dict, node_type: str = None, hop: int = 2, b
274276
result = self.get_hop_infos(attributes, node_type, hop, block_attributes)
275277
return result.paths
276278

277-
def deduplicate_paths(self, result, block_attributes: dict = {}, select_attributes: dict = {}):
279+
def deduplicate_paths(self, result, block_attributes: dict = {}, select_attributes: dict = {}, hop:int=None):
278280
# 获取数据
279281
n0, n1, e, p = result["n0"], result["n1"], result["e"], result["p"]
280282
block_node_ids = [
@@ -308,24 +310,26 @@ def deduplicate_paths(self, result, block_attributes: dict = {}, select_attribut
308310
# 根据保留路径进行合并
309311
nodeid2type = {i["id"]: i["type"] for i in n0+n1}
310312
unique_node_ids = [j for i in new_p for j in i]
311-
last_node_ids = [i[-1] for i in new_p]
313+
last_node_ids = list(set([i[-1] for i in new_p if len(i)>=hop]))
312314
last_node_types = [nodeid2type[i] for i in last_node_ids]
313315
new_n0 = deduplicate_dict([i for i in n0 if i["id"] in unique_node_ids])
314316
new_n1 = deduplicate_dict([i for i in n1 if i["id"] in unique_node_ids])
315317
new_e = deduplicate_dict([i for i in e if i["start_id"] in unique_node_ids and i["end_id"] in unique_node_ids])
316318

317319
return last_node_ids, last_node_types, {"n0": new_n0, "n1": new_n1, "e": new_e, "p": new_p}
318-
320+
319321
def merge_hotinfos(self, result1, result2) -> Dict:
320-
new_n0 = result1["n0"] + result2["n0"]
321-
new_n1 = result1["n1"] + result2["n1"]
322+
old_n0_sets = set([n["id"] for n in result1["n0"]])
323+
old_n1_sets = set([n["id"] for n in result1["n1"]])
324+
new_n0 = result1["n0"] + [n for n in result2["n0"] if n["id"] not in old_n0_sets]
325+
new_n1 = result1["n1"] + [n for n in result2["n1"] if n["id"] not in old_n1_sets]
322326
new_e = result1["e"] + result2["e"]
323-
new_p = result1["p"] + result2["p"] + [
327+
new_p = result1["p"] + [
324328
p_old_1 + p_old_2[1:]
325329
for p_old_1 in result1["p"]
326330
for p_old_2 in result2["p"]
327331
if p_old_2[0] == p_old_1[-1]
328-
]
332+
] # + result2["p"]
329333
new_result = {"n0": new_n0, "n1": new_n1, "e": new_e, "p": new_p}
330334
return new_result
331335

muagent/service/ekg_construct/ekg_construct_base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,9 @@ def returndsl(self, graph_datas_by_path: dict, intents: List[str], ) -> dict:
609609

610610
def get_intents(self, rootid, text: str):
611611
'''according contents search intents'''
612+
if rootid is None or rootid=="":
613+
raise Exception(f"rootid={rootid}, it is empty")
614+
612615
result = self.intention_router.get_intention_by_node_info_nlp(
613616
root_node_id=rootid,
614617
query=text,

muagent/utils/common_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,21 @@ def datefromatToTimestamp(dt, interval=1000, dateformat=DATE_FORMAT):
3838
return int(datetime.strptime(dt, dateformat).timestamp()*interval)
3939

4040

41-
def func_timer():
41+
def func_timer(function):
4242
'''
4343
用装饰器实现函数计时
4444
:param function: 需要计时的函数
4545
:return: None
4646
'''
4747
@wraps(function)
4848
def function_timer(*args, **kwargs):
49+
# logger.info('[Function: {name} start...]'.format(name=function.__name__))
4950
t0 = time.time()
5051
result = function(*args, **kwargs)
5152
t1 = time.time()
5253
logger.info('[Function: {name} finished, spent time: {time:.3f}s]'.format(
5354
name=function.__name__,
54-
time=t1 - t0
55-
))
55+
time=t1 - t0))
5656
return result
5757
return function_timer
5858

0 commit comments

Comments
 (0)