Skip to content

Commit 10105ce

Browse files
committed
feat: implement Paragraph API for CRUD operations and batch deletion
1 parent bcc7c1a commit 10105ce

File tree

5 files changed

+548
-2
lines changed

5 files changed

+548
-2
lines changed

apps/knowledge/api/paragraph.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
from drf_spectacular.types import OpenApiTypes
2+
from drf_spectacular.utils import OpenApiParameter
3+
4+
from common.mixins.api_mixin import APIMixin
5+
from common.result import DefaultResultSerializer, ResultSerializer
6+
from knowledge.serializers.common import BatchSerializer
7+
from knowledge.serializers.paragraph import ParagraphSerializer
8+
from knowledge.serializers.problem import ProblemSerializer
9+
10+
11+
class ParagraphReadResponse(ResultSerializer):
12+
@staticmethod
13+
def get_data():
14+
return ParagraphSerializer(many=True)
15+
16+
17+
class ParagraphReadAPI(APIMixin):
18+
@staticmethod
19+
def get_parameters():
20+
return [
21+
OpenApiParameter(
22+
name="workspace_id",
23+
description="工作空间id",
24+
type=OpenApiTypes.STR,
25+
location='path',
26+
required=True,
27+
),
28+
OpenApiParameter(
29+
name="knowledge_id",
30+
description="知识库id",
31+
type=OpenApiTypes.STR,
32+
location='path',
33+
required=True,
34+
),
35+
OpenApiParameter(
36+
name="document_id",
37+
description="文档id",
38+
type=OpenApiTypes.STR,
39+
location='path',
40+
required=True,
41+
),
42+
OpenApiParameter(
43+
name="title",
44+
description="标题",
45+
type=OpenApiTypes.STR,
46+
location='query',
47+
required=False,
48+
),
49+
OpenApiParameter(
50+
name="content",
51+
description="内容",
52+
type=OpenApiTypes.STR,
53+
location='query',
54+
required=False,
55+
),
56+
]
57+
58+
@staticmethod
59+
def get_response():
60+
return ParagraphReadResponse
61+
62+
63+
class ParagraphCreateAPI(APIMixin):
64+
@staticmethod
65+
def get_parameters():
66+
return [
67+
OpenApiParameter(
68+
name="workspace_id",
69+
description="工作空间id",
70+
type=OpenApiTypes.STR,
71+
location='path',
72+
required=True,
73+
),
74+
OpenApiParameter(
75+
name="knowledge_id",
76+
description="知识库id",
77+
type=OpenApiTypes.STR,
78+
location='path',
79+
required=True,
80+
),
81+
OpenApiParameter(
82+
name="document_id",
83+
description="文档id",
84+
type=OpenApiTypes.STR,
85+
location='path',
86+
required=True,
87+
),
88+
]
89+
90+
@staticmethod
91+
def get_request():
92+
return ParagraphSerializer
93+
94+
@staticmethod
95+
def get_response():
96+
return ParagraphReadResponse
97+
98+
99+
class ParagraphBatchDeleteAPI(ParagraphCreateAPI):
100+
@staticmethod
101+
def get_request():
102+
return BatchSerializer
103+
104+
@staticmethod
105+
def get_response():
106+
return DefaultResultSerializer
107+
108+
109+
class ParagraphGetAPI(APIMixin):
110+
@staticmethod
111+
def get_parameters():
112+
return [
113+
OpenApiParameter(
114+
name="workspace_id",
115+
description="工作空间id",
116+
type=OpenApiTypes.STR,
117+
location='path',
118+
required=True,
119+
),
120+
OpenApiParameter(
121+
name="knowledge_id",
122+
description="知识库id",
123+
type=OpenApiTypes.STR,
124+
location='path',
125+
required=True,
126+
),
127+
OpenApiParameter(
128+
name="document_id",
129+
description="文档id",
130+
type=OpenApiTypes.STR,
131+
location='path',
132+
required=True,
133+
),
134+
OpenApiParameter(
135+
name="paragraph_id",
136+
description="段落id",
137+
type=OpenApiTypes.STR,
138+
location='path',
139+
required=True,
140+
),
141+
]
142+
143+
144+
class ParagraphEditAPI(ParagraphGetAPI):
145+
146+
@staticmethod
147+
def get_request():
148+
return ParagraphSerializer
149+
150+
@staticmethod
151+
def get_response():
152+
return DefaultResultSerializer
153+
154+
155+
class ProblemCreateAPI(ParagraphGetAPI):
156+
@staticmethod
157+
def get_request():
158+
return ProblemSerializer
159+
160+
@staticmethod
161+
def get_response():
162+
return DefaultResultSerializer
163+
164+
165+
class UnAssociationAPI(APIMixin):
166+
@staticmethod
167+
def get_parameters():
168+
return [
169+
OpenApiParameter(
170+
name="workspace_id",
171+
description="工作空间id",
172+
type=OpenApiTypes.STR,
173+
location='path',
174+
required=True,
175+
),
176+
OpenApiParameter(
177+
name="knowledge_id",
178+
description="知识库id",
179+
type=OpenApiTypes.STR,
180+
location='path',
181+
required=True,
182+
),
183+
OpenApiParameter(
184+
name="document_id",
185+
description="文档id",
186+
type=OpenApiTypes.STR,
187+
location='path',
188+
required=True,
189+
),
190+
OpenApiParameter(
191+
name="paragraph_id",
192+
description="段落id",
193+
type=OpenApiTypes.STR,
194+
location='path',
195+
required=True,
196+
),
197+
OpenApiParameter(
198+
name="problem_id",
199+
description="问题id",
200+
type=OpenApiTypes.STR,
201+
location='path',
202+
required=True,
203+
)
204+
]
205+
206+
207+
class AssociationAPI(UnAssociationAPI):
208+
pass

apps/knowledge/serializers/paragraph.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@
88
from django.utils.translation import gettext_lazy as _
99
from rest_framework import serializers
1010

11+
from common.db.search import page_search
1112
from common.exception.app_exception import AppApiException
1213
from common.utils.common import post
1314
from knowledge.models import Paragraph, Problem, Document, ProblemParagraphMapping, SourceType
1415
from knowledge.serializers.common import ProblemParagraphObject, ProblemParagraphManage, \
15-
get_embedding_model_id_by_knowledge_id, update_document_char_length
16+
get_embedding_model_id_by_knowledge_id, update_document_char_length, BatchSerializer
1617
from knowledge.serializers.problem import ProblemInstanceSerializer, ProblemSerializer, ProblemSerializers
1718
from knowledge.task.embedding import embedding_by_paragraph, enable_embedding_by_paragraph, \
1819
disable_embedding_by_paragraph, \
19-
delete_embedding_by_paragraph, embedding_by_problem as embedding_by_problem_task
20+
delete_embedding_by_paragraph, embedding_by_problem as embedding_by_problem_task, delete_embedding_by_paragraph_ids, \
21+
embedding_by_problem, delete_embedding_by_source
2022

2123

2224
class ParagraphSerializer(serializers.ModelSerializer):
@@ -115,6 +117,7 @@ def save(self, instance: Dict, with_valid=True, with_embedding=True, embedding_b
115117
).one(with_valid=True)
116118

117119
class Operate(serializers.Serializer):
120+
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
118121
# 段落id
119122
paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))
120123
# 知识库id
@@ -282,6 +285,100 @@ def or_get(exists_problem_list, content, knowledge_id):
282285
else:
283286
return Problem(id=uuid.uuid7(), content=content, knowledge_id=knowledge_id)
284287

288+
class Query(serializers.Serializer):
289+
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
290+
document_id = serializers.UUIDField(required=True, label=_('document id'))
291+
title = serializers.CharField(required=False, label=_('section title'))
292+
content = serializers.CharField(required=False)
293+
294+
def get_query_set(self):
295+
query_set = QuerySet(model=Paragraph)
296+
query_set = query_set.filter(
297+
**{'knowledge_id': self.data.get('knowledge_id'), 'document_id': self.data.get("document_id")})
298+
if 'title' in self.data:
299+
query_set = query_set.filter(
300+
**{'title__icontains': self.data.get('title')})
301+
if 'content' in self.data:
302+
query_set = query_set.filter(**{'content__icontains': self.data.get('content')})
303+
query_set.order_by('-create_time', 'id')
304+
return query_set
305+
306+
def list(self):
307+
return list(map(lambda row: ParagraphSerializer(row).data, self.get_query_set()))
308+
309+
def page(self, current_page, page_size):
310+
query_set = self.get_query_set()
311+
return page_search(current_page, page_size, query_set, lambda row: ParagraphSerializer(row).data)
312+
313+
class Association(serializers.Serializer):
314+
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
315+
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
316+
problem_id = serializers.UUIDField(required=True, label=_('problem id'))
317+
document_id = serializers.UUIDField(required=True, label=_('document id'))
318+
paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))
319+
320+
def is_valid(self, *, raise_exception=True):
321+
super().is_valid(raise_exception=True)
322+
knowledge_id = self.data.get('knowledge_id')
323+
paragraph_id = self.data.get('paragraph_id')
324+
problem_id = self.data.get("problem_id")
325+
if not QuerySet(Paragraph).filter(knowledge_id=knowledge_id, id=paragraph_id).exists():
326+
raise AppApiException(500, _('Paragraph does not exist'))
327+
if not QuerySet(Problem).filter(knowledge_id=knowledge_id, id=problem_id).exists():
328+
raise AppApiException(500, _('Problem does not exist'))
329+
330+
def association(self, with_valid=True, with_embedding=True):
331+
if with_valid:
332+
self.is_valid(raise_exception=True)
333+
problem = QuerySet(Problem).filter(id=self.data.get("problem_id")).first()
334+
problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid7(),
335+
document_id=self.data.get('document_id'),
336+
paragraph_id=self.data.get('paragraph_id'),
337+
knowledge_id=self.data.get('knowledge_id'),
338+
problem_id=problem.id)
339+
problem_paragraph_mapping.save()
340+
if with_embedding:
341+
model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id'))
342+
embedding_by_problem({
343+
'text': problem.content,
344+
'is_active': True,
345+
'source_type': SourceType.PROBLEM,
346+
'source_id': problem_paragraph_mapping.id,
347+
'document_id': self.data.get('document_id'),
348+
'paragraph_id': self.data.get('paragraph_id'),
349+
'knowledge_id': self.data.get('knowledge_id'),
350+
}, model_id)
351+
352+
def un_association(self, with_valid=True):
353+
if with_valid:
354+
self.is_valid(raise_exception=True)
355+
problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(
356+
paragraph_id=self.data.get('paragraph_id'),
357+
knowledge_id=self.data.get('knowledge_id'),
358+
problem_id=self.data.get(
359+
'problem_id')).first()
360+
problem_paragraph_mapping_id = problem_paragraph_mapping.id
361+
problem_paragraph_mapping.delete()
362+
delete_embedding_by_source(problem_paragraph_mapping_id)
363+
return True
364+
365+
class Batch(serializers.Serializer):
366+
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
367+
document_id = serializers.UUIDField(required=True, label=_('document id'))
368+
369+
@transaction.atomic
370+
def batch_delete(self, instance: Dict, with_valid=True):
371+
if with_valid:
372+
BatchSerializer(data=instance).is_valid(model=Paragraph, raise_exception=True)
373+
self.is_valid(raise_exception=True)
374+
paragraph_id_list = instance.get("id_list")
375+
QuerySet(Paragraph).filter(id__in=paragraph_id_list).delete()
376+
delete_problems_and_mappings(paragraph_id_list)
377+
update_document_char_length(self.data.get('document_id'))
378+
# 删除向量库
379+
delete_embedding_by_paragraph_ids(paragraph_id_list)
380+
return True
381+
285382

286383
def delete_problems_and_mappings(paragraph_ids):
287384
problem_paragraph_mappings = ProblemParagraphMapping.objects.filter(paragraph_id__in=paragraph_ids)

apps/knowledge/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/refresh', views.DocumentView.Refresh.as_view()),
2222
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/cancel_task', views.DocumentView.CancelTask.as_view()),
2323
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/cancel_task/batch', views.DocumentView.BatchCancelTask.as_view()),
24+
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph', views.ParagraphView.as_view()),
25+
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/batch', views.ParagraphView.Batch.as_view()),
26+
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>', views.ParagraphView.Operate.as_view()),
27+
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/problem', views.ParagraphView.Problem.as_view()),
28+
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/problem/<str:problem_id>/association', views.ParagraphView.Association.as_view()),
29+
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/problem/<str:problem_id>/unassociation', views.ParagraphView.UnAssociation.as_view()),
2430
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<int:current_page>/<int:page_sige>', views.DocumentView.Page.as_view()),
2531
path('workspace/<str:workspace_id>/knowledge/<int:current_page>/<int:page_size>', views.KnowledgeView.Page.as_view()),
2632
]

apps/knowledge/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .document import *
22
from .knowledge import *
3+
from .paragraph import *

0 commit comments

Comments
 (0)