Skip to content

Commit 7246867

Browse files
committed
feat: add knowledge management API and permissions
1 parent 83ace97 commit 7246867

File tree

13 files changed

+233
-39
lines changed

13 files changed

+233
-39
lines changed

apps/common/constants/permission_constants.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,11 @@ class PermissionConstants(Enum):
153153
KNOWLEDGE_MODULE_EDIT = Permission(group=Group.KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN,
154154
RoleConstants.USER])
155155
KNOWLEDGE_MODULE_DELETE = Permission(group=Group.KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN,
156-
RoleConstants.USER])
157-
156+
RoleConstants.USER])
157+
KNOWLEDGE_READ = Permission(group=Group.KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN,
158+
RoleConstants.USER])
159+
KNOWLEDGE_CREATE = Permission(group=Group.KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN,
160+
RoleConstants.USER])
158161
def get_workspace_application_permission(self):
159162
return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,
160163
resource_path=

apps/knowledge/api/__init__.py

Whitespace-only changes.

apps/knowledge/api/knowledge.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 ResultSerializer
6+
from knowledge.serializers.knowledge import KnowledgeBaseCreateRequest, KnowledgeModelSerializer
7+
8+
9+
class KnowledgeCreateResponse(ResultSerializer):
10+
def get_data(self):
11+
return KnowledgeModelSerializer()
12+
13+
14+
class KnowledgeCreateAPI(APIMixin):
15+
@staticmethod
16+
def get_parameters():
17+
return [
18+
OpenApiParameter(
19+
name="workspace_id",
20+
description="工作空间id",
21+
type=OpenApiTypes.STR,
22+
location='path',
23+
required=True,
24+
)
25+
]
26+
27+
@staticmethod
28+
def get_request():
29+
return KnowledgeBaseCreateRequest
30+
31+
@staticmethod
32+
def get_response():
33+
return KnowledgeCreateResponse
34+
35+
36+
class KnowledgeTreeReadAPI(APIMixin):
37+
@staticmethod
38+
def get_parameters():
39+
return [
40+
OpenApiParameter(
41+
name="workspace_id",
42+
description="工作空间id",
43+
type=OpenApiTypes.STR,
44+
location='path',
45+
required=True,
46+
),
47+
OpenApiParameter(
48+
name="module_id",
49+
description="模块id",
50+
type=OpenApiTypes.STR,
51+
location='query',
52+
required=False,
53+
)
54+
]

apps/knowledge/migrations/0001_initial.py

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ def insert_default_data(apps, schema_editor):
1414
KnowledgeModule.objects.create(id='root', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab')
1515

1616

17-
1817
class Migration(migrations.Migration):
19-
2018
initial = True
2119

2220
dependencies = [
@@ -30,8 +28,12 @@ class Migration(migrations.Migration):
3028
fields=[
3129
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
3230
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
33-
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
31+
('id',
32+
models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False,
33+
verbose_name='主键id')),
3434
('file_name', models.CharField(default='', max_length=256, verbose_name='文件名称')),
35+
('workspace_id',
36+
models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
3537
('loid', models.IntegerField(verbose_name='loid')),
3638
('meta', models.JSONField(default=dict, verbose_name='文件关联数据')),
3739
],
@@ -40,39 +42,60 @@ class Migration(migrations.Migration):
4042
},
4143
),
4244
migrations.CreateModel(
43-
name='Knowledge',
45+
name='KnowledgeModule',
4446
fields=[
4547
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
4648
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
47-
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
48-
('name', models.CharField(max_length=150, verbose_name='知识库名称')),
49-
('desc', models.CharField(max_length=256, verbose_name='描述')),
50-
('type', models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')], default=0, verbose_name='类型')),
51-
('meta', models.JSONField(default=dict, verbose_name='元数据')),
52-
('embedding_mode', models.ForeignKey(default=knowledge.models.knowledge.default_model, on_delete=django.db.models.deletion.DO_NOTHING, to='models_provider.model', verbose_name='向量模型')),
53-
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user', verbose_name='所属用户')),
49+
('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False,
50+
verbose_name='主键id')),
51+
('name', models.CharField(max_length=64, verbose_name='文件夹名称')),
52+
('workspace_id',
53+
models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
54+
('lft', models.PositiveIntegerField(editable=False)),
55+
('rght', models.PositiveIntegerField(editable=False)),
56+
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
57+
('level', models.PositiveIntegerField(editable=False)),
58+
('parent',
59+
mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE,
60+
related_name='children', to='knowledge.knowledgemodule')),
61+
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user',
62+
verbose_name='用户id')),
5463
],
5564
options={
56-
'db_table': 'knowledge',
65+
'db_table': 'knowledge_module',
5766
},
5867
),
5968
migrations.CreateModel(
60-
name='KnowledgeModule',
69+
name='Knowledge',
6170
fields=[
6271
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
6372
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
64-
('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')),
65-
('name', models.CharField(max_length=64, verbose_name='文件夹名称')),
66-
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
67-
('lft', models.PositiveIntegerField(editable=False)),
68-
('rght', models.PositiveIntegerField(editable=False)),
69-
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
70-
('level', models.PositiveIntegerField(editable=False)),
71-
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='knowledge.knowledgemodule')),
72-
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')),
73+
('id',
74+
models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False,
75+
verbose_name='主键id')),
76+
('name', models.CharField(max_length=150, verbose_name='知识库名称')),
77+
('workspace_id',
78+
models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
79+
('desc', models.CharField(max_length=256, verbose_name='描述')),
80+
('type',
81+
models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')],
82+
default=0, verbose_name='类型')),
83+
('meta', models.JSONField(default=dict, verbose_name='元数据')),
84+
('scope',
85+
models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用')], default='WORKSPACE',
86+
max_length=20, verbose_name='可用范围')),
87+
('module',
88+
models.ForeignKey(default='root', on_delete=django.db.models.deletion.CASCADE,
89+
to='knowledge.knowledgemodule',
90+
verbose_name='模块id')),
91+
('embedding_model', models.ForeignKey(default=knowledge.models.knowledge.default_model,
92+
on_delete=django.db.models.deletion.DO_NOTHING,
93+
to='models_provider.model', verbose_name='向量模型')),
94+
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user',
95+
verbose_name='所属用户')),
7396
],
7497
options={
75-
'db_table': 'knowledge_module',
98+
'db_table': 'knowledge',
7699
},
77100
),
78101
migrations.RunPython(insert_default_data),

apps/knowledge/models/knowledge.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@
1212

1313

1414
class KnowledgeType(models.IntegerChoices):
15-
base = 0, '通用类型'
16-
web = 1, 'web站点类型'
17-
lark = 2, '飞书类型'
18-
yuque = 3, '语雀类型'
15+
BASE = 0, '通用类型'
16+
WEB = 1, 'web站点类型'
17+
LARK = 2, '飞书类型'
18+
YUQUE = 3, '语雀类型'
19+
20+
21+
class KnowledgeScope(models.TextChoices):
22+
SHARED = "SHARED", '共享'
23+
WORKSPACE = "WORKSPACE", "工作空间可用"
1924

2025

2126
def default_model():
@@ -26,7 +31,7 @@ def default_model():
2631
class KnowledgeModule(MPTTModel, AppModelMixin):
2732
id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name="主键id")
2833
name = models.CharField(max_length=64, verbose_name="文件夹名称")
29-
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id")
34+
user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name="用户id")
3035
workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True)
3136
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
3237

@@ -37,13 +42,17 @@ class MPTTMeta:
3742
order_insertion_by = ['name']
3843

3944

45+
4046
class Knowledge(AppModelMixin):
4147
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id")
4248
name = models.CharField(max_length=150, verbose_name="知识库名称")
49+
workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True)
4350
desc = models.CharField(max_length=256, verbose_name="描述")
4451
user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name="所属用户")
45-
type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.base)
46-
embedding_mode = models.ForeignKey(Model, on_delete=models.DO_NOTHING, verbose_name="向量模型",
52+
type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.BASE)
53+
scope = models.CharField(max_length=20, verbose_name='可用范围', choices=KnowledgeScope.choices, default=KnowledgeScope.WORKSPACE)
54+
module = models.ForeignKey(KnowledgeModule, on_delete=models.CASCADE, verbose_name="模块id", default='root')
55+
embedding_model = models.ForeignKey(Model, on_delete=models.DO_NOTHING, verbose_name="向量模型",
4756
default=default_model)
4857
meta = models.JSONField(verbose_name="元数据", default=dict)
4958

@@ -54,6 +63,7 @@ class Meta:
5463
class File(AppModelMixin):
5564
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id")
5665
file_name = models.CharField(max_length=256, verbose_name="文件名称", default="")
66+
workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True)
5767
loid = models.IntegerField(verbose_name="loid")
5868
meta = models.JSONField(verbose_name="文件关联数据", default=dict)
5969

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# coding=utf-8
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import uuid_utils as uuid
2+
from django.utils.translation import gettext_lazy as _
3+
from rest_framework import serializers
4+
5+
from knowledge.models import Knowledge, KnowledgeScope, KnowledgeType
6+
7+
8+
class KnowledgeModelSerializer(serializers.ModelSerializer):
9+
class Meta:
10+
model = Knowledge
11+
fields = ['id', 'name', 'desc', 'meta', 'module_id', 'type', 'workspace_id', 'create_time', 'update_time']
12+
13+
14+
class KnowledgeBaseCreateRequest(serializers.Serializer):
15+
name = serializers.CharField(required=True, label=_('knowledge name'))
16+
desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description'))
17+
embedding = serializers.CharField(required=True, label=_('knowledge embedding'))
18+
19+
class KnowledgeWebCreateRequest(serializers.Serializer):
20+
name = serializers.CharField(required=True, label=_('knowledge name'))
21+
desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description'))
22+
embedding = serializers.CharField(required=True, label=_('knowledge embedding'))
23+
24+
25+
class KnowledgeSerializer(serializers.Serializer):
26+
class Create(serializers.Serializer):
27+
user_id = serializers.UUIDField(required=True, label=_('user id'))
28+
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
29+
30+
def insert(self, instance, with_valid=True):
31+
if with_valid:
32+
self.is_valid(raise_exception=True)
33+
KnowledgeBaseCreateRequest(data=instance).is_valid(raise_exception=True)
34+
knowledge = Knowledge(
35+
id=uuid.uuid7(),
36+
name=instance.get('name'),
37+
workspace_id=self.data.get('workspace_id'),
38+
desc=instance.get('desc'),
39+
type=instance.get('type', KnowledgeType.BASE),
40+
user_id=self.data.get('user_id'),
41+
scope=KnowledgeScope.WORKSPACE,
42+
module_id=instance.get('module_id', 'root'),
43+
embedding_model_id=instance.get('embedding'),
44+
meta=instance.get('meta', {}),
45+
)
46+
knowledge.save()
47+
return KnowledgeModelSerializer(knowledge).data
48+
49+
50+
class KnowledgeTreeSerializer(serializers.Serializer):
51+
def get_knowledge_list(self, param):
52+
pass

apps/knowledge/urls.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
from django.shortcuts import render
1+
from django.urls import path
22

3-
# Create your views here.
3+
from . import views
4+
5+
app_name = "knowledge"
6+
urlpatterns = [
7+
path('workspace/<str:workspace_id>/knowledge', views.KnowledgeView.as_view()),
8+
]

apps/knowledge/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .knowledge import *

apps/knowledge/views/knowledge.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from django.utils.translation import gettext_lazy as _
2+
from drf_spectacular.utils import extend_schema
3+
from rest_framework.request import Request
4+
from rest_framework.views import APIView
5+
6+
from common.auth import TokenAuth
7+
from common.auth.authentication import has_permissions
8+
from common.constants.permission_constants import PermissionConstants
9+
from common.result import result
10+
from knowledge.api.knowledge import KnowledgeCreateAPI, KnowledgeTreeReadAPI
11+
from knowledge.serializers.knowledge import KnowledgeSerializer, KnowledgeTreeSerializer
12+
13+
14+
class KnowledgeView(APIView):
15+
authentication_classes = [TokenAuth]
16+
17+
@extend_schema(
18+
methods=['POST'],
19+
description=_('Create knowledge'),
20+
operation_id=_('Create knowledge'),
21+
parameters=KnowledgeCreateAPI.get_parameters(),
22+
request=KnowledgeCreateAPI.get_request(),
23+
responses=KnowledgeCreateAPI.get_response(),
24+
tags=[_('Knowledge Base')]
25+
)
26+
@has_permissions(PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission())
27+
def post(self, request: Request, workspace_id: str):
28+
return result.success(KnowledgeSerializer.Create(
29+
data={'user_id': request.user.id, 'workspace_id': workspace_id}
30+
).insert(request.data))
31+
32+
@extend_schema(
33+
methods=['GET'],
34+
description=_('Get knowledge by module'),
35+
operation_id=_('Get knowledge by module'),
36+
parameters=KnowledgeTreeReadAPI.get_parameters(),
37+
responses=KnowledgeTreeReadAPI.get_response(),
38+
tags=[_('Knowledge Base')]
39+
)
40+
@has_permissions(PermissionConstants.KNOWLEDGE_READ.get_workspace_permission())
41+
def get(self, request: Request, workspace_id: str):
42+
return result.success(KnowledgeTreeSerializer(
43+
data={'workspace_id': workspace_id}
44+
).get_knowledge_list(request.query_params.get('module_id')))

0 commit comments

Comments
 (0)