From 013ddcf0340ed7e76e550d73a6a626f2dfdf5cf0 Mon Sep 17 00:00:00 2001 From: FFXN Date: Tue, 25 Aug 2026 16:30:46 +0800 Subject: [PATCH] fix(knowledge_fs): accept direct unpublished profile activation responses and validate settings --- api/services/knowledge_fs/data_facade.py | 19 ++++- .../services/test_knowledge_fs_data_facade.py | 71 +++++++++++++++++++ ...-08-25-direct-profile-settings-response.md | 17 +++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 knowledge-fs/.harness/changes/2026-08-25-direct-profile-settings-response.md diff --git a/api/services/knowledge_fs/data_facade.py b/api/services/knowledge_fs/data_facade.py index 3f3a2d82569..35f564c0f84 100644 --- a/api/services/knowledge_fs/data_facade.py +++ b/api/services/knowledge_fs/data_facade.py @@ -48,6 +48,7 @@ from services.knowledge_fs.product_dto import ( KnowledgeFSDocumentRevisionListResponse, KnowledgeFSDocumentUploadAcceptedResponse, KnowledgeFSDurableDeletionAcceptedResponse, + KnowledgeFSEmbeddingSettingsResponse, KnowledgeFSFindQuery, KnowledgeFSGoldenQuestionBulkImportPayload, KnowledgeFSGoldenQuestionBulkImportRemotePayload, @@ -93,6 +94,7 @@ from services.knowledge_fs.product_dto import ( KnowledgeFSResearchTaskPlanResponse, KnowledgeFSResearchTaskResponse, KnowledgeFSRetrievalProfileUpdatePayload, + KnowledgeFSRetrievalSettingsResponse, KnowledgeFSSettingsPayload, KnowledgeFSSettingsResponse, KnowledgeFSSettingsUpdateResponse, @@ -561,10 +563,21 @@ class KnowledgeFSDataFacade: ) else: raise KnowledgeFSProductRequestRejectedError(status_code=422) - return KnowledgeFSSettingsUpdateResponse( - migration=KnowledgeFSProfileMigrationResponse.model_validate(migration_raw), - settings=current, + if isinstance(migration_raw, dict) and ("runState" in migration_raw or "run_state" in migration_raw): + return KnowledgeFSSettingsUpdateResponse( + migration=KnowledgeFSProfileMigrationResponse.model_validate(migration_raw), + settings=current, + ) + if payload.embedding is not None: + KnowledgeFSEmbeddingSettingsResponse.model_validate(migration_raw) + else: + KnowledgeFSRetrievalSettingsResponse.model_validate(migration_raw) + refreshed = self.get_settings( + tenant_id=tenant_id, + account_id=account_id, + control_space_id=control_space_id, ) + return KnowledgeFSSettingsUpdateResponse(settings=refreshed) raw = self._interactive( tenant_id=tenant_id, diff --git a/api/tests/unit_tests/services/test_knowledge_fs_data_facade.py b/api/tests/unit_tests/services/test_knowledge_fs_data_facade.py index d5d29c2437b..074d60d8f20 100644 --- a/api/tests/unit_tests/services/test_knowledge_fs_data_facade.py +++ b/api/tests/unit_tests/services/test_knowledge_fs_data_facade.py @@ -1287,6 +1287,77 @@ def test_active_settings_use_durable_profile_migration_routes() -> None: } +def test_active_settings_accept_direct_unpublished_profile_activation() -> None: + class DirectActivationRemote(ActiveSettingsRemote): + def execute_json(self, request: KnowledgeFSRemoteJSONRequest): + if request.operation_id == "updateRetrievalProfile": + self.requests.append(request) + return { + "defaultMode": "fast", + "reasoningModel": { + "model": "reason-v1", + "pluginId": "plugin-1", + "provider": "provider-1", + }, + "rerank": { + "enabled": True, + "model": { + "model": "rerank-v1", + "pluginId": "plugin-1", + "provider": "provider-1", + }, + }, + "revision": 5, + "scoreThreshold": {"enabled": True, "stage": "rerank", "value": 0.51}, + "topK": 3, + } + return super().execute_json(request) + + remote = DirectActivationRemote() + facade = KnowledgeFSDataFacade( + broker=RecordingBroker(), + remote=remote, + ) # type: ignore[arg-type] + + result = facade.update_settings( + tenant_id="tenant-1", + account_id="account-1", + control_space_id="control-1", + payload=KnowledgeFSSettingsPayload( + expected_revision=9, + retrieval=KnowledgeFSProductRetrievalProfile( + default_mode="fast", + reasoning_model=KnowledgeFSProfileModelSelection( + model="reason-v1", + plugin_id="plugin-1", + provider="provider-1", + ), + rerank=KnowledgeFSProductRerankProfile( + enabled=True, + model=KnowledgeFSProfileModelSelection( + model="rerank-v1", + plugin_id="plugin-1", + provider="provider-1", + ), + ), + score_threshold=KnowledgeFSProductScoreThreshold( + enabled=True, + stage="rerank", + value=0.51, + ), + top_k=3, + ), + ), + ) + + assert result.migration is None + assert [request.operation_id for request in remote.requests] == [ + "getSettings", + "updateRetrievalProfile", + "getSettings", + ] + + def test_setup_required_settings_without_active_profile_use_product_settings_route() -> None: remote = ActiveSettingsRemote( active_profile_available=False, diff --git a/knowledge-fs/.harness/changes/2026-08-25-direct-profile-settings-response.md b/knowledge-fs/.harness/changes/2026-08-25-direct-profile-settings-response.md new file mode 100644 index 00000000000..aeef2f59cb4 --- /dev/null +++ b/knowledge-fs/.harness/changes/2026-08-25-direct-profile-settings-response.md @@ -0,0 +1,17 @@ +# Accept direct unpublished profile activation responses + +## Summary + +- Accept either an asynchronous profile migration response or a direct profile response from KnowledgeFS profile update operations. +- Validate direct embedding and retrieval profile responses, then reload settings so the console receives the committed revision. +- Add regression coverage for direct unpublished retrieval profile activation. + +## Verification + +- `uv run --project api pytest api/tests/unit_tests/services/test_knowledge_fs_data_facade.py` (90 passed) +- `uv run --project api ruff check api/services/knowledge_fs/data_facade.py api/tests/unit_tests/services/test_knowledge_fs_data_facade.py` +- `uv run --project api ruff format --check api/services/knowledge_fs/data_facade.py api/tests/unit_tests/services/test_knowledge_fs_data_facade.py` + +## Deployment note + +Profile activation can synchronously perform model capability preflight. Deployments using the 10-second default `KNOWLEDGE_FS_TIMEOUT_SECONDS` may time out before KnowledgeFS returns; increase the API-side value within its supported 60-second maximum and refresh settings before retrying an ambiguously timed-out mutation.