From 85049ca5f6f6d8178d947480f3be9e9ba5f6d371 Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Thu, 12 Nov 2026 18:21:14 +0800 Subject: [PATCH 1/2] refactor: update string filtering to be case-insensitive for improved search functionality --- apps/homepage/serializers/homepage.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/homepage/serializers/homepage.py b/apps/homepage/serializers/homepage.py index d0698db715b..9339c60b2e0 100644 --- a/apps/homepage/serializers/homepage.py +++ b/apps/homepage/serializers/homepage.py @@ -245,7 +245,7 @@ def get_queryset(self, auth): ).exclude(chat_user_id="") if name: - base_queryset = base_queryset.filter(asker__username__contains=name) + base_queryset = base_queryset.filter(asker__username__icontains=name) # ---- 权限过滤 ---- base_queryset = self._apply_permission_filter(base_queryset, auth, workspace_id, user_id) @@ -329,7 +329,7 @@ def export(self, auth, with_valid=True): ] worksheet.append(row) response = HttpResponse(content_type="application/vnd.ms-excel") - response["Content-Disposition"] = f'attachment; filename="data.xlsx"' + response["Content-Disposition"] = 'attachment; filename="data.xlsx"' workbook.save(response) return response @@ -400,9 +400,10 @@ def get_queryset(self, auth): queryset = QuerySet(Application) is_resource_filter = True if name: - queryset = queryset.filter(name__contains=name) + queryset = queryset.filter(name__icontains=name) if is_workspace_manage(auth, workspace_id): queryset = queryset.filter(workspace_id=workspace_id) + is_resource_filter = False elif is_extends_workspace_manage(auth, workspace_id): if has_extends_workspace_manage_permission( auth, PermissionConstants.APPLICATION_READ.value, workspace_id @@ -500,7 +501,7 @@ def export(self, auth, with_valid=True): ] worksheet.append(row) response = HttpResponse(content_type="application/vnd.ms-excel") - response["Content-Disposition"] = f'attachment; filename="data.xlsx"' + response["Content-Disposition"] = 'attachment; filename="data.xlsx"' workbook.save(response) return response @@ -532,7 +533,7 @@ def get_queryset(self, auth): is_resource_filter = True queryset = QuerySet(Application) if name: - queryset = queryset.filter(name__contains=name) + queryset = queryset.filter(name__icontains=name) if is_workspace_manage(auth, workspace_id): queryset = queryset.filter(workspace_id=workspace_id) is_resource_filter = False @@ -630,7 +631,7 @@ def export(self, auth, with_valid=True): ] worksheet.append(row) response = HttpResponse(content_type="application/vnd.ms-excel") - response["Content-Disposition"] = f'attachment; filename="data.xlsx"' + response["Content-Disposition"] = 'attachment; filename="data.xlsx"' workbook.save(response) return response From 90a14ef7ceabddf873c3f73c2c899f682a179d3b Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Thu, 12 Nov 2026 18:30:49 +0800 Subject: [PATCH 2/2] refactor: add client parameter handling to prevent sending sensitive config in request body --- .../qianfan_model_provider/credential/llm.py | 11 ++- .../qianfan_model_provider/model/image.py | 6 ++ .../impl/qianfan_model_provider/model/llm.py | 7 ++ .../tests/test_qianfan_model.py | 79 +++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 apps/models_provider/tests/test_qianfan_model.py diff --git a/apps/models_provider/impl/qianfan_model_provider/credential/llm.py b/apps/models_provider/impl/qianfan_model_provider/credential/llm.py index 5cc2afb467b..bb188ff85f1 100644 --- a/apps/models_provider/impl/qianfan_model_provider/credential/llm.py +++ b/apps/models_provider/impl/qianfan_model_provider/credential/llm.py @@ -70,7 +70,16 @@ def is_valid( model.invoke([HumanMessage(content="1")]) except Exception as e: maxkb_logger.error(f"Exception: {e}", exc_info=True) - raise e + if isinstance(e, AppApiException): + raise e + if raise_exception: + raise AppApiException( + ValidCode.valid_error.value, + gettext("Verification failed, please check whether the parameters are correct: {error}").format( + error=str(e) + ), + ) + return False return True def encryption_dict(self, model_info: Dict[str, object]): diff --git a/apps/models_provider/impl/qianfan_model_provider/model/image.py b/apps/models_provider/impl/qianfan_model_provider/model/image.py index bd99d8a5534..68d98c52b0e 100644 --- a/apps/models_provider/impl/qianfan_model_provider/model/image.py +++ b/apps/models_provider/impl/qianfan_model_provider/model/image.py @@ -19,9 +19,15 @@ def is_cache_model(): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) + # request_timeout / max_retries 是 OpenAI 客户端配置项,不能随 extra_body 发送到接口请求体。 + client_params = {} + for key in ("request_timeout", "max_retries"): + if optional_params.get(key) is not None: + client_params[key] = optional_params.pop(key) return QianfanVisionModel( model=model_name, openai_api_base=model_credential.get("api_base"), openai_api_key=model_credential.get("api_key"), + **client_params, extra_body=optional_params, ) diff --git a/apps/models_provider/impl/qianfan_model_provider/model/llm.py b/apps/models_provider/impl/qianfan_model_provider/model/llm.py index e08c404ba18..81f173dec0a 100644 --- a/apps/models_provider/impl/qianfan_model_provider/model/llm.py +++ b/apps/models_provider/impl/qianfan_model_provider/model/llm.py @@ -23,9 +23,16 @@ def is_cache_model(): @staticmethod def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs): optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs) + # request_timeout / max_retries 是 OpenAI 客户端配置项,不能随 extra_body 发送到接口请求体, + # 否则千帆 v2 网关可能返回 500 internal_error,且短超时/关闭重试不会真正生效。 + client_params = {} + for key in ("request_timeout", "max_retries"): + if optional_params.get(key) is not None: + client_params[key] = optional_params.pop(key) return QianfanChatModel( model=model_name, openai_api_base=model_credential.get("api_base"), openai_api_key=model_credential.get("api_key"), + **client_params, extra_body=optional_params, ) diff --git a/apps/models_provider/tests/test_qianfan_model.py b/apps/models_provider/tests/test_qianfan_model.py new file mode 100644 index 00000000000..5a61259365a --- /dev/null +++ b/apps/models_provider/tests/test_qianfan_model.py @@ -0,0 +1,79 @@ +# coding=utf-8 +from unittest.mock import MagicMock + +from django.test import SimpleTestCase +from langchain_core.messages import HumanMessage + +from common.exception.app_exception import AppApiException +from models_provider.impl.qianfan_model_provider.credential.llm import QianfanLLMModelCredential +from models_provider.impl.qianfan_model_provider.model.image import QianfanVisionModel +from models_provider.impl.qianfan_model_provider.model.llm import QianfanChatModel + +CRED = {"api_base": "https://qianfan.baidubce.com/v2", "api_key": "sk-test"} + + +class QianfanModelConstructionTests(SimpleTestCase): + def test_client_params_are_not_sent_in_request_body(self): + for model_class in (QianfanChatModel, QianfanVisionModel): + with self.subTest(model=model_class.__name__): + model = model_class.new_instance( + "LLM", + "ernie-5.1", + CRED, + temperature=0.95, + max_tokens=1, + request_timeout=5.0, + max_retries=0, + ) + + # 客户端配置项应真正作用于 OpenAI 客户端 + self.assertEqual(5.0, model.request_timeout) + self.assertEqual(0, model.max_retries) + + # 且不应混入接口请求体 + self.assertNotIn("request_timeout", model.extra_body) + self.assertNotIn("max_retries", model.extra_body) + + payload = model._get_request_payload([HumanMessage(content="1")]) + self.assertNotIn("request_timeout", payload) + self.assertNotIn("max_retries", payload) + + +class QianfanLLMCredentialTests(SimpleTestCase): + def _mock_provider(self, model): + provider = MagicMock() + provider.get_model.return_value = model + return provider + + def test_validation_propagates_provider_error_message(self): + model = MagicMock() + model.invoke.side_effect = RuntimeError("Error code: 500 - {'error': {'code': 'internal_error'}}") + + credential = QianfanLLMModelCredential() + with self.assertRaises(AppApiException) as raised: + credential.is_valid( + "LLM", + "ernie-5.1", + CRED, + {"temperature": 0.95}, + self._mock_provider(model), + raise_exception=True, + ) + + self.assertIn("Error code: 500 - {'error': {'code': 'internal_error'}}", raised.exception.message) + + def test_validation_returns_false_when_not_raising(self): + model = MagicMock() + model.invoke.side_effect = RuntimeError("boom") + + credential = QianfanLLMModelCredential() + result = credential.is_valid( + "LLM", + "ernie-5.1", + CRED, + {"temperature": 0.95}, + self._mock_provider(model), + raise_exception=False, + ) + + self.assertFalse(result)