diff --git a/apps/common/constants/permission_constants.py b/apps/common/constants/permission_constants.py index ed1154066..21de8b16c 100644 --- a/apps/common/constants/permission_constants.py +++ b/apps/common/constants/permission_constants.py @@ -21,6 +21,8 @@ class Group(Enum): MODEL = "MODEL" + TOOL = "TOOL" + class Operate(Enum): """ @@ -111,8 +113,7 @@ class PermissionConstants(Enum): RoleConstants.USER]) USER_EDIT = Permission(group=Group.USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN]) USER_DELETE = Permission(group=Group.USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN]) - TOOL_CREATE = Permission(group=Group.USER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, - RoleConstants.USER]) + MODEL_CREATE = Permission(group=Group.MODEL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER]) MODEL_READ = Permission(group=Group.MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, @@ -121,6 +122,15 @@ class PermissionConstants(Enum): role_list=[RoleConstants.ADMIN, RoleConstants.USER]) MODEL_DELETE = Permission(group=Group.MODEL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER]) + TOOL_MODULE_CREATE = Permission(group=Group.TOOL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) + TOOL_MODULE_READ = Permission(group=Group.TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) + TOOL_MODULE_EDIT = Permission(group=Group.TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) + + TOOL_CREATE = Permission(group=Group.TOOL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) def get_workspace_application_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, diff --git a/apps/maxkb/urls.py b/apps/maxkb/urls.py index 341d8d3c9..133e259a2 100644 --- a/apps/maxkb/urls.py +++ b/apps/maxkb/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ path("api/", include("users.urls")), path("api/", include("tools.urls")), path("api/", include("models_provider.urls")), + path("api/", include("modules.urls")) ] urlpatterns += [ path('schema/', SpectacularAPIView.as_view(), name='schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的 diff --git a/apps/modules/__init__.py b/apps/modules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/modules/api/__init__.py b/apps/modules/api/__init__.py new file mode 100644 index 000000000..9bad5790a --- /dev/null +++ b/apps/modules/api/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/apps/modules/api/module.py b/apps/modules/api/module.py new file mode 100644 index 000000000..27a3d5fab --- /dev/null +++ b/apps/modules/api/module.py @@ -0,0 +1,105 @@ +# coding=utf-8 +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter + +from common.mixins.api_mixin import APIMixin +from common.result import ResultSerializer +from modules.models.module import ModuleCreateRequest, ModuleEditRequest +from modules.serializers.module import ModuleSerializer + + +class ModuleCreateResponse(ResultSerializer): + def get_data(self): + return ModuleSerializer() + + +class ModuleCreateAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="source", + description="菜单", + type=OpenApiTypes.STR, + enum=["APPLICATION", "KNOWLEDGE", "TOOL"], + location='path', + required=True, + ) + ] + + @staticmethod + def get_request(): + return ModuleCreateRequest + + @staticmethod + def get_response(): + return ModuleCreateResponse + + +class ModuleReadAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="source", + description="菜单", + type=OpenApiTypes.STR, + enum=["APPLICATION", "KNOWLEDGE", "TOOL"], + location='path', + required=True, + ), + OpenApiParameter( + name="module_id", + description="模块id", + type=OpenApiTypes.STR, + location='path', + required=True, + ) + ] + + @staticmethod + def get_response(): + return ModuleCreateResponse + + +class ModuleEditAPI(ModuleReadAPI): + + @staticmethod + def get_request(): + return ModuleEditRequest + + +class ModuleTreeReadAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="source", + description="菜单", + type=OpenApiTypes.STR, + enum=["APPLICATION", "KNOWLEDGE", "TOOL"], + location='path', + required=True, + ) + ] diff --git a/apps/modules/models/__init__.py b/apps/modules/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/modules/models/module.py b/apps/modules/models/module.py new file mode 100644 index 000000000..362ed0153 --- /dev/null +++ b/apps/modules/models/module.py @@ -0,0 +1,13 @@ +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + + +class ModuleCreateRequest(serializers.Serializer): + name = serializers.CharField(required=True, label=_('module name')) + + parent_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, default='root', + label=_('parent id')) + + +class ModuleEditRequest(serializers.Serializer): + name = serializers.CharField(required=True, label=_('module name')) \ No newline at end of file diff --git a/apps/modules/serializers/__init__.py b/apps/modules/serializers/__init__.py new file mode 100644 index 000000000..9bad5790a --- /dev/null +++ b/apps/modules/serializers/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/apps/modules/serializers/module.py b/apps/modules/serializers/module.py new file mode 100644 index 000000000..98d947505 --- /dev/null +++ b/apps/modules/serializers/module.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +import uuid_utils.compat as uuid +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from common.constants.permission_constants import Group +from modules.api.module import ModuleCreateRequest +from tools.models import ToolModule +from tools.serializers.tool_module import ToolModuleTreeSerializer + + +def get_module_type(source): + if source == Group.TOOL.name: + return ToolModule + elif source == Group.APPLICATION.name: + # todo app module + return None + elif source == Group.KNOWLEDGE.name: + # todo knowledge module + return None + else: + return None + + +class ModuleSerializer(serializers.Serializer): + id = serializers.CharField(required=True, label=_('module id')) + name = serializers.CharField(required=True, label=_('module name')) + user_id = serializers.CharField(required=True, label=_('module user id')) + workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('workspace id')) + parent_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('parent id')) + + class Create(serializers.Serializer): + user_id = serializers.UUIDField(required=True, label=_('user id')) + source = serializers.CharField(required=True, label=_('source')) + + def insert(self, instance, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + ModuleCreateRequest(data=instance).is_valid(raise_exception=True) + + workspace_id = self.data.get('workspace_id', 'default') + parent_id = instance.get('parent_id', 'root') + name = instance.get('name') + + Module = get_module_type(self.data.get('source')) + if QuerySet(Module).filter(name=name, workspace_id=workspace_id, parent_id=parent_id).exists(): + raise serializers.ValidationError(_('Module name already exists')) + + module = Module( + id=uuid.uuid7(), + name=instance.get('name'), + user_id=self.data.get('user_id'), + workspace_id=workspace_id, + parent_id=parent_id + ) + module.save() + return ModuleSerializer(module).data + + class Operate(serializers.Serializer): + id = serializers.CharField(required=True, label=_('module id')) + workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id')) + source = serializers.CharField(required=True, label=_('source')) + + def edit(self, instance): + self.is_valid(raise_exception=True) + Module = get_module_type(self.data.get('source')) + if not QuerySet(Module).filter(id=self.data.get('id')).exists(): + raise serializers.ValidationError(_('Module does not exist')) + + edit_field_list = ['name'] + edit_dict = {field: instance.get(field) for field in edit_field_list if ( + field in instance and instance.get(field) is not None)} + + QuerySet(Module).filter(id=self.data.get('id')).update(**edit_dict) + + return self.one() + + def one(self): + self.is_valid(raise_exception=True) + Module = get_module_type(self.data.get('source')) + module = QuerySet(Module).filter(id=self.data.get('id')).first() + return ModuleSerializer(module).data + + +class ModuleTreeSerializer(serializers.Serializer): + workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id')) + source = serializers.CharField(required=True, label=_('source')) + + def get_module_tree(self): + self.is_valid(raise_exception=True) + Module = get_module_type(self.data.get('source')) + nodes = Module.objects.filter(workspace_id=self.data.get('workspace_id')).get_cached_trees() + serializer = ToolModuleTreeSerializer(nodes, many=True) + return serializer.data # 这是可序列化的字典 diff --git a/apps/modules/urls.py b/apps/modules/urls.py new file mode 100644 index 000000000..0b5687f9e --- /dev/null +++ b/apps/modules/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + +app_name = "module" +urlpatterns = [ + path('workspace///module', views.ModuleView.Create.as_view()), + path('workspace///module/tree', views.ModuleTreeView.as_view()), + path('workspace///module/', views.ModuleView.Operate.as_view()), +] diff --git a/apps/modules/views/__init__.py b/apps/modules/views/__init__.py new file mode 100644 index 000000000..267692405 --- /dev/null +++ b/apps/modules/views/__init__.py @@ -0,0 +1 @@ +from .module import * diff --git a/apps/modules/views/module.py b/apps/modules/views/module.py new file mode 100644 index 000000000..d1353a2b3 --- /dev/null +++ b/apps/modules/views/module.py @@ -0,0 +1,79 @@ +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.utils import extend_schema +from rest_framework.request import Request +from rest_framework.views import APIView + +from common.auth import TokenAuth +from common.auth.authentication import has_permissions +from common.constants.permission_constants import Permission, Group, Operate +from common.result import result +from modules.api.module import ModuleCreateAPI, ModuleEditAPI, ModuleReadAPI, ModuleTreeReadAPI +from modules.serializers.module import ModuleSerializer, ModuleTreeSerializer + + +class ModuleView(APIView): + class Create(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(methods=['POST'], + description=_('Create module'), + operation_id=_('Create module'), + parameters=ModuleCreateAPI.get_parameters(), + request=ModuleCreateAPI.get_request(), + responses=ModuleCreateAPI.get_response(), + tags=[_('Module')]) + @has_permissions(lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.CREATE, + resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}")) + def post(self, request: Request, workspace_id: str, source: str): + return result.success(ModuleSerializer.Create( + data={'user_id': request.user.id, + 'source': source, + 'workspace_id': workspace_id} + ).insert(request.data)) + + class Operate(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(methods=['PUT'], + description=_('Update module'), + operation_id=_('Update module'), + parameters=ModuleEditAPI.get_parameters(), + request=ModuleEditAPI.get_request(), + responses=ModuleEditAPI.get_response(), + tags=[_('Module')]) + @has_permissions(lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.EDIT, + resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}")) + def put(self, request: Request, workspace_id: str, source: str, module_id: str): + return result.success(ModuleSerializer.Operate( + data={'id': module_id, 'workspace_id': workspace_id, 'source': source} + ).edit(request.data)) + + @extend_schema(methods=['GET'], + description=_('Get module'), + operation_id=_('Get module'), + parameters=ModuleReadAPI.get_parameters(), + responses=ModuleReadAPI.get_response(), + tags=[_('Module')]) + @has_permissions(lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.READ, + resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}")) + def get(self, request: Request, workspace_id: str, source: str, module_id: str): + return result.success(ModuleSerializer.Operate( + data={'id': module_id, 'workspace_id': workspace_id, 'source': source} + ).one()) + + +class ModuleTreeView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(methods=['GET'], + description=_('Get module tree'), + operation_id=_('Get module tree'), + parameters=ModuleTreeReadAPI.get_parameters(), + responses=ModuleTreeReadAPI.get_response(), + tags=[_('Module')]) + @has_permissions(lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.READ, + resource_path=f"/WORKSPACE/{kwargs.get('workspace_id')}")) + def get(self, request: Request, workspace_id: str, source: str): + return result.success(ModuleTreeSerializer( + data={'workspace_id': workspace_id, 'source': source} + ).get_module_tree()) diff --git a/apps/tools/migrations/0001_initial.py b/apps/tools/migrations/0001_initial.py index fab62935e..84874d309 100644 --- a/apps/tools/migrations/0001_initial.py +++ b/apps/tools/migrations/0001_initial.py @@ -1,12 +1,19 @@ -# Generated by Django 5.2 on 2025-04-17 06:03 +# Generated by Django 5.2 on 2025-04-18 04:07 import django.db.models.deletion +import mptt.fields import uuid_utils.compat from django.db import migrations, models +from tools.models import ToolModule + + +def insert_default_data(apps, schema_editor): + # 创建一个根模块(没有父节点) + ToolModule.objects.create(id='root', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab') + class Migration(migrations.Migration): - initial = True dependencies = [ @@ -14,10 +21,37 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='ToolModule', + fields=[ + ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, + verbose_name='主键id')), + ('name', models.CharField(max_length=64, verbose_name='文件夹名称')), + ('workspace_id', + models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), + ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ('parent', + mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='children', to='tools.toolmodule')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', + verbose_name='用户id')), + ], + options={ + 'db_table': 'tool_module', + }, + ), + migrations.RunPython(insert_default_data), migrations.CreateModel( name='Tool', fields=[ - ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), + ('id', + models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, + verbose_name='主键id')), ('name', models.CharField(max_length=64, verbose_name='函数名称')), ('desc', models.CharField(max_length=128, verbose_name='描述')), ('code', models.CharField(max_length=102400, verbose_name='python代码')), @@ -25,14 +59,22 @@ class Migration(migrations.Migration): ('init_field_list', models.JSONField(default=list, verbose_name='启动字段列表')), ('icon', models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='函数库icon')), ('is_active', models.BooleanField(default=True)), - ('scope', models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用')], default='WORKSPACE', max_length=20, verbose_name='可用范围')), - ('tool_type', models.CharField(choices=[('INTERNAL', '内置'), ('PUBLIC', '公开')], default='PUBLIC', max_length=20, verbose_name='函数类型')), + ('scope', + models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用')], default='WORKSPACE', + max_length=20, verbose_name='可用范围')), + ('tool_type', + models.CharField(choices=[('INTERNAL', '内置'), ('PUBLIC', '公开')], default='PUBLIC', max_length=20, + verbose_name='函数类型')), ('template_id', models.UUIDField(default=None, null=True, verbose_name='模版id')), - ('module_id', models.CharField(default='root', max_length=64, null=True, verbose_name='模块id')), + ('workspace_id', models.CharField(default='default', max_length=64, verbose_name='工作空间id')), ('init_params', models.CharField(max_length=102400, null=True, verbose_name='初始化参数')), ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', + verbose_name='用户id')), + ('module_id', + models.ForeignKey(default='root', on_delete=django.db.models.deletion.CASCADE, to='tools.toolmodule', + verbose_name='模块id')), ], options={ 'db_table': 'tool', diff --git a/apps/tools/models/__init__.py b/apps/tools/models/__init__.py index 8c9d8dde4..c7f6bcfcb 100644 --- a/apps/tools/models/__init__.py +++ b/apps/tools/models/__init__.py @@ -1 +1,4 @@ +# -*- coding: utf-8 -*- + from .tool import * +from .tool_module import * diff --git a/apps/tools/models/tool.py b/apps/tools/models/tool.py index 00cb0808d..045b4d5fd 100644 --- a/apps/tools/models/tool.py +++ b/apps/tools/models/tool.py @@ -1,6 +1,7 @@ import uuid_utils.compat as uuid from django.db import models +from .tool_module import ToolModule from users.models import User @@ -29,7 +30,8 @@ class Tool(models.Model): tool_type = models.CharField(max_length=20, verbose_name='函数类型', choices=ToolType.choices, default=ToolType.PUBLIC) template_id = models.UUIDField(max_length=128, verbose_name="模版id", null=True, default=None) - module_id = models.CharField(max_length=64, verbose_name="模块id", null=True, default='root') + module_id = models.ForeignKey(ToolModule, on_delete=models.CASCADE, verbose_name="模块id", default='root') + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default") init_params = models.CharField(max_length=102400, verbose_name="初始化参数", null=True) create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True) update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, null=True) diff --git a/apps/tools/models/tool_module.py b/apps/tools/models/tool_module.py new file mode 100644 index 000000000..2e61d4dd0 --- /dev/null +++ b/apps/tools/models/tool_module.py @@ -0,0 +1,21 @@ +from django.db import models +from mptt.fields import TreeForeignKey +from mptt.models import MPTTModel + +from users.models import User + + +class ToolModule(MPTTModel): + id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name="主键id") + name = models.CharField(max_length=64, verbose_name="文件夹名称") + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id") + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) + parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') + create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True) + update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True, null=True) + + class Meta: + db_table = "tool_module" + + class MPTTMeta: + order_insertion_by = ['name'] diff --git a/apps/tools/serializers/tool.py b/apps/tools/serializers/tool.py index 3ecd14fbd..d7f4c3ddb 100644 --- a/apps/tools/serializers/tool.py +++ b/apps/tools/serializers/tool.py @@ -13,7 +13,7 @@ class ToolModelSerializer(serializers.ModelSerializer): class Meta: model = Tool fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'init_params', - 'scope', 'is_active', 'user_id', 'template_id', + 'scope', 'is_active', 'user_id', 'template_id', 'workspace_id', 'module_id', 'create_time', 'update_time'] @@ -38,12 +38,14 @@ class ToolCreateRequest(serializers.Serializer): code = serializers.CharField(required=True, label=_('tool content')) - input_field_list = serializers.ListField(child=ToolInputField(), required=True, label=_('input field list')) + input_field_list = serializers.ListField(child=ToolInputField(), required=False, label=_('input field list')) init_field_list = serializers.ListField(required=False, default=list, label=_('init field list')) is_active = serializers.BooleanField(required=False, label=_('Is active')) + module_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, default='root') + class ToolSerializer(serializers.Serializer): class Create(serializers.Serializer): @@ -58,8 +60,8 @@ class ToolSerializer(serializers.Serializer): desc=instance.get('desc'), code=instance.get('code'), user_id=self.data.get('user_id'), - input_field_list=instance.get('input_field_list'), - init_field_list=instance.get('init_field_list'), + input_field_list=instance.get('input_field_list', []), + init_field_list=instance.get('init_field_list', []), scope=ToolScope.WORKSPACE, is_active=False) tool.save() diff --git a/apps/tools/serializers/tool_module.py b/apps/tools/serializers/tool_module.py new file mode 100644 index 000000000..24151ecdf --- /dev/null +++ b/apps/tools/serializers/tool_module.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from rest_framework import serializers + +from tools.models import ToolModule + + +class ToolModuleTreeSerializer(serializers.ModelSerializer): + children = serializers.SerializerMethodField() + + class Meta: + model = ToolModule + fields = ['id', 'name', 'user_id', 'workspace_id', 'parent_id', 'children'] + + def get_children(self, obj): + return ToolModuleTreeSerializer(obj.get_children(), many=True).data diff --git a/pyproject.toml b/pyproject.toml index dbefd0c14..305e1886c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,34 +11,34 @@ django = "5.2" drf-spectacular = { extras = ["sidecar"], version = "0.28.0" } django-redis = "5.4.0" django-db-connection-pool = "1.2.5" +django-mptt = "0.17.0" psycopg = { extras = ["binary"], version = "3.2.6" } python-dotenv = "1.1.0" uuid-utils = "0.10.0" diskcache2 = "0.1.2" captcha = "0.7.1" -langchain-openai = "^0.3.0" -langchain-anthropic = "^0.3.0" -langchain-community = "^0.3.0" -langchain-deepseek = "^0.1.0" -langchain-google-genai = "^2.0.9" -langchain-mcp-adapters = "^0.0.5" -langchain-huggingface = "^0.1.2" -langchain-ollama = "^0.3.0" -langgraph = "^0.3.0" -mcp = "^1.4.1" -qianfan = "^0.3.6.1" -zhipuai = "^2.0.1" -boto3 = "^1.34.160" -tencentcloud-sdk-python = "^3.0.1209" -xinference-client = "^1.3.0" -anthropic = "^0.49.0" -dashscope = "^1.17.0" +langchain-openai = "0.3.14" +langchain-anthropic = "0.3.12" +langchain-community = "0.3.21" +langchain-deepseek = "0.1.3" +langchain-google-genai = "2.1.3" +langchain-mcp-adapters = "0.0.8" +langchain-huggingface = "0.1.2" +langchain-ollama = "0.3.2" +langgraph = "0.3.31" +qianfan = "0.3.18" +zhipuai = "2.1.5.20250415" +boto3 = "1.37.36" +tencentcloud-sdk-python = "3.0.1362" +xinference-client = "1.4.1" +anthropic = "0.49.0" +dashscope = "1.23.1" pylint = "3.1.0" -pydub = "^0.25.1" -cffi = "^1.17.1" -pysilk = "^0.0.1" -sentence-transformers = "^4.0.2" -websockets = "^13.0" +pydub = "0.25.1" +cffi = "1.17.1" +pysilk = "0.0.1" +sentence-transformers = "4.1.0" +websockets = "13.1" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"