Skip to content

Commit fa24268

Browse files
authored
feat: Migrate Search Document Node (#6468)
1 parent fa40755 commit fa24268

2 files changed

Lines changed: 221 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@file: __init__.py
5+
@desc:
6+
"""
7+
from .search_document_node import SearchDocumentNode
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@file: search_document_node.py
5+
@desc:
6+
"""
7+
from typing import List
8+
9+
import jieba
10+
from django.db.models import Q
11+
from django.db.models import QuerySet
12+
from django.utils.translation import gettext_lazy as _
13+
from rest_framework import serializers
14+
15+
from application.workflow.common import WorkflowType
16+
from application.workflow.i_node import INode
17+
from common.constants.permission_constants import RoleConstants
18+
from common.database_model_manage.database_model_manage import DatabaseModelManage
19+
from common.utils.shared_resource_auth import filter_authorized_ids
20+
from knowledge.models import Document, DocumentTag, Knowledge
21+
22+
23+
class SearchDocumentNodeSerializer(serializers.Serializer):
24+
knowledge_id_list = serializers.ListField(
25+
required=False, child=serializers.UUIDField(required=True),
26+
label=_("knowledge id list"), default=list
27+
)
28+
search_mode = serializers.ChoiceField(
29+
required=False, choices=['auto', 'custom'], label=_("search mode"), default='auto'
30+
)
31+
search_scope_type = serializers.ChoiceField(
32+
required=False, choices=['custom', 'referencing'], label=_("search scope type"),
33+
allow_null=True, default='custom'
34+
)
35+
search_scope_source = serializers.ChoiceField(
36+
required=False, choices=['document', 'knowledge'],
37+
label=_("search scope variable type"), default='knowledge'
38+
)
39+
search_scope_reference = serializers.ListField(
40+
required=False, label=_("search scope variable"), default=list
41+
)
42+
question_reference = serializers.ListField(
43+
required=False, label=_("question reference address"), default=list
44+
)
45+
search_condition_type = serializers.ChoiceField(
46+
required=False, choices=['AND', 'OR'], label=_("search condition type"), default='AND'
47+
)
48+
search_condition_list = serializers.ListField(
49+
required=False, label=_("search condition list"), default=list
50+
)
51+
52+
53+
def _handle_auto_tags(workflow_manage, document_id_list, question_reference):
54+
question = workflow_manage.get_reference_field(question_reference[0], question_reference[1:]) if question_reference else ''
55+
keywords = jieba.lcut(str(question))
56+
if not keywords:
57+
return set()
58+
59+
q_objects = Q()
60+
for keyword in keywords:
61+
q_objects |= Q(tag__value__icontains=keyword)
62+
63+
matched_doc_ids = set(
64+
QuerySet(DocumentTag)
65+
.filter(document_id__in=document_id_list)
66+
.filter(q_objects)
67+
.values_list('document_id', flat=True)
68+
.distinct()
69+
)
70+
return matched_doc_ids
71+
72+
73+
def _handle_custom_tags(workflow_manage, document_id_list, search_condition_list, search_condition_type):
74+
if not search_condition_list:
75+
return set(document_id_list)
76+
77+
if search_condition_type == 'AND':
78+
matched_doc_ids = set(document_id_list)
79+
for condition in search_condition_list:
80+
tag_key = condition['key']
81+
field_value = workflow_manage.generate_prompt(condition['value'])
82+
compare_type = condition['compare']
83+
84+
if not field_value or field_value == 'None' or len(field_value) == 0:
85+
continue
86+
87+
if compare_type == 'not_contain':
88+
exclude_docs = set(QuerySet(DocumentTag).filter(
89+
document_id__in=matched_doc_ids,
90+
tag__key=tag_key,
91+
tag__value__icontains=field_value
92+
).values_list('document_id', flat=True).distinct())
93+
matched_doc_ids = matched_doc_ids - exclude_docs
94+
else:
95+
if compare_type == 'contain':
96+
q_filter = Q(tag__key=tag_key, tag__value__icontains=field_value)
97+
elif compare_type == 'eq':
98+
q_filter = Q(tag__key=tag_key, tag__value=field_value)
99+
else:
100+
continue
101+
102+
tag_docs = set(QuerySet(DocumentTag).filter(
103+
document_id__in=matched_doc_ids
104+
).filter(q_filter).values_list('document_id', flat=True).distinct())
105+
matched_doc_ids = matched_doc_ids.intersection(tag_docs)
106+
107+
return matched_doc_ids
108+
else:
109+
matched_docs = set()
110+
for condition in search_condition_list:
111+
tag_key = condition['key']
112+
field_value = workflow_manage.generate_prompt(condition['value'])
113+
compare_type = condition['compare']
114+
115+
if not field_value or field_value == 'None' or len(field_value) == 0:
116+
continue
117+
118+
if compare_type == 'not_contain':
119+
exclude_docs = set(QuerySet(DocumentTag).filter(
120+
document_id__in=document_id_list,
121+
tag__key=tag_key,
122+
tag__value__icontains=field_value
123+
).values_list('document_id', flat=True).distinct())
124+
matched_docs = matched_docs.union(set(document_id_list) - exclude_docs)
125+
else:
126+
if compare_type == 'contain':
127+
q_filter = Q(tag__key=tag_key, tag__value__icontains=field_value)
128+
elif compare_type == 'eq':
129+
q_filter = Q(tag__key=tag_key, tag__value=field_value)
130+
else:
131+
continue
132+
133+
docs = set(QuerySet(DocumentTag).filter(
134+
document_id__in=document_id_list
135+
).filter(q_filter).values_list('document_id', flat=True).distinct())
136+
matched_docs = matched_docs.union(docs)
137+
138+
return matched_docs
139+
140+
141+
class SearchDocumentNode(INode):
142+
serializer_class = SearchDocumentNodeSerializer
143+
supported_workflow_type_list = [WorkflowType.APPLICATION, WorkflowType.TOOL]
144+
type = 'search-document-node'
145+
146+
def execute(self):
147+
node_params = self.get_parameters()
148+
workflow_params = self.get_workflow_parameters()
149+
150+
knowledge_id_list = node_params.get('knowledge_id_list', [])
151+
search_mode = node_params.get('search_mode', 'auto')
152+
search_scope_type = node_params.get('search_scope_type', 'custom')
153+
search_scope_source = node_params.get('search_scope_source', 'knowledge')
154+
search_scope_reference = node_params.get('search_scope_reference', [])
155+
question_reference = node_params.get('question_reference', [])
156+
search_condition_type = node_params.get('search_condition_type', 'AND')
157+
search_condition_list = node_params.get('search_condition_list', [])
158+
159+
workspace_id = workflow_params.get('workspace_id')
160+
161+
if search_scope_type == 'custom':
162+
knowledge_id_list = filter_authorized_ids('knowledge', knowledge_id_list, workspace_id)
163+
document_id_list = list(QuerySet(Document).filter(
164+
knowledge_id__in=knowledge_id_list
165+
).values_list('id', flat=True))
166+
else:
167+
if search_scope_source == 'document':
168+
document_id_list = self.workflow_manage.get_reference_field(
169+
search_scope_reference[0], search_scope_reference[1:]
170+
) if search_scope_reference else []
171+
else:
172+
ref_knowledge_ids = self.workflow_manage.get_reference_field(
173+
search_scope_reference[0], search_scope_reference[1:]
174+
) if search_scope_reference else []
175+
ref_knowledge_ids = filter_authorized_ids('knowledge', ref_knowledge_ids, workspace_id)
176+
document_id_list = list(QuerySet(Document).filter(
177+
knowledge_id__in=ref_knowledge_ids
178+
).values_list('id', flat=True))
179+
180+
get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')
181+
chat_user_type = workflow_params.get('chat_user_type')
182+
183+
if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:
184+
actual_knowledge_ids = list(
185+
QuerySet(Document).filter(id__in=document_id_list)
186+
.values_list('knowledge_id', flat=True).distinct()
187+
)
188+
authorized_knowledge_ids = get_knowledge_list_of_authorized(
189+
workflow_params.get('chat_user_id'),
190+
[str(k_id) for k_id in actual_knowledge_ids]
191+
)
192+
document_id_list = list(QuerySet(Document).filter(
193+
id__in=document_id_list,
194+
knowledge_id__in=authorized_knowledge_ids
195+
).values_list('id', flat=True))
196+
197+
if search_mode == 'auto':
198+
matched_doc_ids = _handle_auto_tags(self.workflow_manage, document_id_list, question_reference)
199+
final_document_ids = list(matched_doc_ids)
200+
else:
201+
matched_document_ids = _handle_custom_tags(
202+
self.workflow_manage, document_id_list, search_condition_list, search_condition_type
203+
)
204+
final_document_ids = list(matched_document_ids)
205+
206+
final_document_ids = [str(doc_id) for doc_id in final_document_ids]
207+
document_items = list(QuerySet(Document).filter(id__in=final_document_ids).values())
208+
final_knowledge_ids = list(set(str(doc['knowledge_id']) for doc in document_items))
209+
knowledge_items = list(QuerySet(Knowledge).filter(id__in=final_knowledge_ids).values())
210+
211+
self.write_context('document_list', final_document_ids)
212+
self.write_context('document_items', document_items)
213+
self.write_context('knowledge_list', final_knowledge_ids)
214+
self.write_context('knowledge_items', knowledge_items)

0 commit comments

Comments
 (0)