From 5af52fee9592c2163e45662e7176487d139a1559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=E2=82=82=E2=82=82H=E2=82=82=E2=82=85NO=E2=82=86?= <96930391+Sisyphbaous-DT-Project@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:33:29 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix(kb):=20=E6=A0=A1=E9=AA=8C=20FAISS=20?= =?UTF-8?q?=E7=B4=A2=E5=BC=95=E7=BB=B4=E5=BA=A6=E4=B8=8E=E5=B5=8C=E5=85=A5?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=98=AF=E5=90=A6=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db/vec_db/faiss_impl/embedding_storage.py | 16 +++++ astrbot/core/db/vec_db/faiss_impl/vec_db.py | 23 ++++--- tests/unit/test_faiss_vec_db.py | 62 +++++++++++++++++-- 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py b/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py index dc6977cf8a..8ef5a09300 100644 --- a/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +++ b/astrbot/core/db/vec_db/faiss_impl/embedding_storage.py @@ -8,6 +8,8 @@ import numpy as np +from astrbot.core.exceptions import KnowledgeBaseUploadError + class EmbeddingStorage: def __init__(self, dimension: int, path: str | None = None) -> None: @@ -16,6 +18,20 @@ def __init__(self, dimension: int, path: str | None = None) -> None: self.index = None if path and os.path.exists(path): self.index = faiss.read_index(path) + actual_dimension = self.index.d + if actual_dimension != dimension: + raise KnowledgeBaseUploadError( + stage="embedding", + user_message=( + "向量化失败:知识库索引维度与当前嵌入模型维度不一致" + f"(索引维度 {actual_dimension},当前模型配置维度 {dimension})。" + "请使用原嵌入模型,或删除并重建知识库索引。" + ), + details={ + "index_dimension": actual_dimension, + "provider_dimension": dimension, + }, + ) else: base_index = faiss.IndexFlatL2(dimension) self.index = faiss.IndexIDMap(base_index) diff --git a/astrbot/core/db/vec_db/faiss_impl/vec_db.py b/astrbot/core/db/vec_db/faiss_impl/vec_db.py index 0474683754..70d9423f6a 100644 --- a/astrbot/core/db/vec_db/faiss_impl/vec_db.py +++ b/astrbot/core/db/vec_db/faiss_impl/vec_db.py @@ -109,13 +109,22 @@ async def insert_batch( start = time.time() logger.debug(f"Generating embeddings for {len(contents)} contents...") - vectors = await self.embedding_provider.get_embeddings_batch( - contents, - batch_size=batch_size, - tasks_limit=tasks_limit, - max_retries=max_retries, - progress_callback=progress_callback, - ) + try: + vectors = await self.embedding_provider.get_embeddings_batch( + contents, + batch_size=batch_size, + tasks_limit=tasks_limit, + max_retries=max_retries, + progress_callback=progress_callback, + ) + except KnowledgeBaseUploadError: + raise + except Exception as exc: + raise KnowledgeBaseUploadError( + stage="embedding", + user_message=f"向量化失败:批量生成嵌入向量时出错。{exc}", + details={"content_count": content_count}, + ) from exc end = time.time() logger.debug( f"Generated embeddings for {len(contents)} contents in {end - start:.2f} seconds.", diff --git a/tests/unit/test_faiss_vec_db.py b/tests/unit/test_faiss_vec_db.py index d294d51cd3..8039ae21dd 100644 --- a/tests/unit/test_faiss_vec_db.py +++ b/tests/unit/test_faiss_vec_db.py @@ -1,7 +1,8 @@ -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, MagicMock, patch import pytest +from astrbot.core.db.vec_db.faiss_impl.embedding_storage import EmbeddingStorage from astrbot.core.db.vec_db.faiss_impl.vec_db import FaissVecDB from astrbot.core.exceptions import KnowledgeBaseUploadError @@ -22,9 +23,7 @@ async def test_insert_batch_skips_empty_contents() -> None: @pytest.mark.asyncio -async def test_insert_batch_raises_friendly_error_for_embedding_count_mismatch() -> ( - None -): +async def test_insert_batch_raises_friendly_error_for_embedding_count_mismatch() -> None: vec_db = FaissVecDB.__new__(FaissVecDB) vec_db.embedding_provider = AsyncMock() vec_db.embedding_provider.get_embeddings_batch.return_value = [[0.1, 0.2]] @@ -40,7 +39,58 @@ async def test_insert_batch_raises_friendly_error_for_embedding_count_mismatch() ids=["doc-1", "doc-2"], ) - assert "向量化失败" in str(exc_info.value) - assert "期望 2,实际 1" in str(exc_info.value) + assert exc_info.value.stage == "embedding" + assert "expected_contents" in exc_info.value.details + assert exc_info.value.details["expected_contents"] == 2 + assert exc_info.value.details["actual_vectors"] == 1 vec_db.document_storage.insert_documents_batch.assert_not_awaited() vec_db.embedding_storage.insert_batch.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_insert_batch_wraps_embedding_batch_failures_as_embedding_error() -> None: + vec_db = FaissVecDB.__new__(FaissVecDB) + vec_db.embedding_provider = AsyncMock() + vec_db.embedding_provider.get_embeddings_batch.side_effect = Exception("rate limit") + vec_db.document_storage = AsyncMock() + vec_db.embedding_storage = AsyncMock() + vec_db.embedding_storage.dimension = 2 + + with pytest.raises(KnowledgeBaseUploadError) as exc_info: + await FaissVecDB.insert_batch( + vec_db, + contents=["chunk-1"], + metadatas=[{}], + ids=["doc-1"], + ) + + assert exc_info.value.stage == "embedding" + assert "批量生成嵌入向量时出错" in str(exc_info.value) + assert "rate limit" in str(exc_info.value) + vec_db.document_storage.insert_documents_batch.assert_not_awaited() + vec_db.embedding_storage.insert_batch.assert_not_awaited() + + +def test_embedding_storage_rejects_existing_index_dimension_mismatch() -> None: + mock_index = MagicMock() + mock_index.d = 768 + + with ( + patch( + "astrbot.core.db.vec_db.faiss_impl.embedding_storage.os.path.exists", + return_value=True, + ), + patch( + "astrbot.core.db.vec_db.faiss_impl.embedding_storage.faiss.read_index", + return_value=mock_index, + ), + ): + with pytest.raises(KnowledgeBaseUploadError) as exc_info: + EmbeddingStorage(1536, "existing-index.faiss") + + assert exc_info.value.stage == "embedding" + assert "知识库索引维度与当前嵌入模型维度不一致" in str(exc_info.value) + assert exc_info.value.details == { + "index_dimension": 768, + "provider_dimension": 1536, + } From a5275e658a8a52ddbe542da3793eb1231465b1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=E2=82=82=E2=82=82H=E2=82=82=E2=82=85NO=E2=82=86?= <96930391+Sisyphbaous-DT-Project@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:48:28 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(kb):=20=E5=9C=A8=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=B8=AD=E6=9A=B4=E9=9C=B2=E7=BB=B4=E5=BA=A6?= =?UTF-8?q?=E4=B8=8D=E5=8C=B9=E9=85=8D=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/knowledge_base/kb_mgr.py | 35 +++--- tests/unit/test_faiss_vec_db.py | 22 +++- tests/unit/test_kb_manager_resilience.py | 130 +++++++++++++---------- 3 files changed, 118 insertions(+), 69 deletions(-) diff --git a/astrbot/core/knowledge_base/kb_mgr.py b/astrbot/core/knowledge_base/kb_mgr.py index 8dea163cbf..81fc90e764 100644 --- a/astrbot/core/knowledge_base/kb_mgr.py +++ b/astrbot/core/knowledge_base/kb_mgr.py @@ -1,6 +1,7 @@ from pathlib import Path from astrbot.core import logger +from astrbot.core.exceptions import KnowledgeBaseUploadError from astrbot.core.provider.manager import ProviderManager from astrbot.core.utils.astrbot_path import get_astrbot_knowledge_base_path @@ -198,6 +199,19 @@ async def update_kb( } previous_init_error = kb_helper.init_error + def rollback_state() -> None: + kb.kb_name = previous_state["kb_name"] + kb.description = previous_state["description"] + kb.emoji = previous_state["emoji"] + kb.embedding_provider_id = previous_state["embedding_provider_id"] + kb.rerank_provider_id = previous_state["rerank_provider_id"] + kb.chunk_size = previous_state["chunk_size"] + kb.chunk_overlap = previous_state["chunk_overlap"] + kb.top_k_dense = previous_state["top_k_dense"] + kb.top_k_sparse = previous_state["top_k_sparse"] + kb.top_m_final = previous_state["top_m_final"] + kb_helper.init_error = previous_init_error + if kb_name is not None: kb.kb_name = kb_name if description is not None: @@ -229,24 +243,21 @@ async def update_kb( try: await new_helper.initialize() + except KnowledgeBaseUploadError as e: + rollback_state() + logger.error( + f"知识库 {kb.kb_name}({kb.kb_id}) 重新初始化失败,继续使用旧实例: {e}", + exc_info=True, + ) + raise ValueError(str(e)) from e except Exception as e: # Roll back in-memory settings and keep current helper available. - kb.kb_name = previous_state["kb_name"] - kb.description = previous_state["description"] - kb.emoji = previous_state["emoji"] - kb.embedding_provider_id = previous_state["embedding_provider_id"] - kb.rerank_provider_id = previous_state["rerank_provider_id"] - kb.chunk_size = previous_state["chunk_size"] - kb.chunk_overlap = previous_state["chunk_overlap"] - kb.top_k_dense = previous_state["top_k_dense"] - kb.top_k_sparse = previous_state["top_k_sparse"] - kb.top_m_final = previous_state["top_m_final"] - kb_helper.init_error = previous_init_error + rollback_state() logger.error( f"知识库 {kb.kb_name}({kb.kb_id}) 重新初始化失败,继续使用旧实例: {e}", exc_info=True, ) - return kb_helper + raise ValueError(f"知识库重新初始化失败:{e}") from e async with self.kb_db.get_db() as session: session.add(kb) diff --git a/tests/unit/test_faiss_vec_db.py b/tests/unit/test_faiss_vec_db.py index 8039ae21dd..4cf2152aa3 100644 --- a/tests/unit/test_faiss_vec_db.py +++ b/tests/unit/test_faiss_vec_db.py @@ -40,7 +40,7 @@ async def test_insert_batch_raises_friendly_error_for_embedding_count_mismatch() ) assert exc_info.value.stage == "embedding" - assert "expected_contents" in exc_info.value.details + assert "期望 2,实际 1" in str(exc_info.value) assert exc_info.value.details["expected_contents"] == 2 assert exc_info.value.details["actual_vectors"] == 1 vec_db.document_storage.insert_documents_batch.assert_not_awaited() @@ -94,3 +94,23 @@ def test_embedding_storage_rejects_existing_index_dimension_mismatch() -> None: "index_dimension": 768, "provider_dimension": 1536, } + + +def test_embedding_storage_accepts_existing_index_dimension_match() -> None: + mock_index = MagicMock() + mock_index.d = 768 + + with ( + patch( + "astrbot.core.db.vec_db.faiss_impl.embedding_storage.os.path.exists", + return_value=True, + ), + patch( + "astrbot.core.db.vec_db.faiss_impl.embedding_storage.faiss.read_index", + return_value=mock_index, + ), + ): + storage = EmbeddingStorage(768, "existing-index.faiss") + + assert storage.index is mock_index + assert storage.dimension == 768 diff --git a/tests/unit/test_kb_manager_resilience.py b/tests/unit/test_kb_manager_resilience.py index ed43a338f8..2f4ab5b729 100644 --- a/tests/unit/test_kb_manager_resilience.py +++ b/tests/unit/test_kb_manager_resilience.py @@ -2,7 +2,7 @@ Unit tests for knowledge base manager resilience behavior. Tests the following scenarios: -1. update_kb preserves old instance when re-initialization fails +1. update_kb surfaces re-initialization failures while preserving the old helper 2. update_kb switches instance only after new instance initializes successfully 3. _ensure_vec_db clears stale init_error after successful initialization @@ -17,6 +17,8 @@ import pytest +from astrbot.core.exceptions import KnowledgeBaseUploadError + @pytest.fixture def stub_provider_manager_module(): @@ -61,7 +63,6 @@ def mock_kb_db(): @pytest.fixture def mock_knowledge_base(): """Create a mock KnowledgeBase instance.""" - # Use lazy import to avoid circular import from astrbot.core.knowledge_base.models import KnowledgeBase kb = KnowledgeBase( @@ -88,7 +89,7 @@ def mock_embedding_provider(): @pytest.mark.asyncio -async def test_update_kb_preserves_old_instance_when_reinit_fails( +async def test_update_kb_raises_error_and_preserves_old_instance_when_reinit_fails( stub_provider_manager_module, mock_provider_manager, mock_kb_db, @@ -96,17 +97,14 @@ async def test_update_kb_preserves_old_instance_when_reinit_fails( mock_embedding_provider, ): """ - Test that update_kb preserves the old KBHelper instance when - re-initialization fails, ensuring the knowledge base remains available. + Test that update_kb surfaces re-initialization failures while + preserving the old KBHelper instance for continued availability. """ - # Lazy import to avoid circular import from astrbot.core.knowledge_base.kb_helper import KBHelper from astrbot.core.knowledge_base.kb_mgr import KnowledgeBaseManager - # Setup: create an existing KBHelper with working vec_db mock_provider_manager.get_provider_by_id.return_value = mock_embedding_provider - # Create KBHelper using __new__ to avoid __init__ side effects old_helper = KBHelper.__new__(KBHelper) old_helper.kb = mock_knowledge_base old_helper.prov_mgr = mock_provider_manager @@ -114,41 +112,81 @@ async def test_update_kb_preserves_old_instance_when_reinit_fails( old_helper.kb_root_dir = "/tmp/test_kb" old_helper.chunker = MagicMock() old_helper.init_error = None - old_helper.vec_db = MagicMock() # Simulate existing working vec_db + old_helper.vec_db = MagicMock() old_helper.terminate = AsyncMock() - # Create KBManager and inject the existing helper kb_mgr = KnowledgeBaseManager.__new__(KnowledgeBaseManager) kb_mgr.provider_manager = mock_provider_manager kb_mgr.kb_db = mock_kb_db kb_mgr.kb_insts = {mock_knowledge_base.kb_id: old_helper} kb_mgr.retrieval_manager = MagicMock() - # Mock KBHelper creation to simulate initialization failure with patch.object(KBHelper, "initialize", new_callable=AsyncMock) as mock_init: - # First call (for new_helper) should fail mock_init.side_effect = Exception("Embedding provider unavailable") - # Execute update_kb with a different embedding provider - result = await kb_mgr.update_kb( - kb_id=mock_knowledge_base.kb_id, - kb_name="updated_kb", - embedding_provider_id="new-embedding-provider", - ) + with pytest.raises(ValueError) as exc_info: + await kb_mgr.update_kb( + kb_id=mock_knowledge_base.kb_id, + kb_name="updated_kb", + embedding_provider_id="new-embedding-provider", + ) - # Verify: the old helper should be returned, not a new one - assert result is not None - assert result is old_helper - assert kb_mgr.kb_insts[mock_knowledge_base.kb_id] is old_helper + assert "Embedding provider unavailable" in str(exc_info.value) + assert kb_mgr.kb_insts[mock_knowledge_base.kb_id] is old_helper + assert hasattr(old_helper, "vec_db") + assert old_helper.vec_db is not None + assert old_helper.init_error is None + assert old_helper.kb.kb_name == "test_kb" + assert old_helper.kb.embedding_provider_id == "test-embedding-provider" - # Verify: old helper's vec_db should still be available - assert hasattr(result, "vec_db") - assert result.vec_db is not None - # Verify: failure does not replace the existing helper state - assert result.init_error is None - assert result.kb.kb_name == "test_kb" - assert result.kb.embedding_provider_id == "test-embedding-provider" +@pytest.mark.asyncio +async def test_update_kb_raises_user_facing_error_for_dimension_mismatch( + stub_provider_manager_module, + mock_provider_manager, + mock_kb_db, + mock_knowledge_base, + mock_embedding_provider, +): + from astrbot.core.knowledge_base.kb_helper import KBHelper + from astrbot.core.knowledge_base.kb_mgr import KnowledgeBaseManager + + mock_provider_manager.get_provider_by_id.return_value = mock_embedding_provider + + old_helper = KBHelper.__new__(KBHelper) + old_helper.kb = mock_knowledge_base + old_helper.prov_mgr = mock_provider_manager + old_helper.kb_db = mock_kb_db + old_helper.kb_root_dir = "/tmp/test_kb" + old_helper.chunker = MagicMock() + old_helper.init_error = None + old_helper.vec_db = MagicMock() + old_helper.terminate = AsyncMock() + + kb_mgr = KnowledgeBaseManager.__new__(KnowledgeBaseManager) + kb_mgr.provider_manager = mock_provider_manager + kb_mgr.kb_db = mock_kb_db + kb_mgr.kb_insts = {mock_knowledge_base.kb_id: old_helper} + kb_mgr.retrieval_manager = MagicMock() + + with patch.object(KBHelper, "initialize", new_callable=AsyncMock) as mock_init: + mock_init.side_effect = KnowledgeBaseUploadError( + stage="embedding", + user_message="知识库索引维度与当前嵌入模型维度不一致", + details={"index_dimension": 768, "provider_dimension": 1536}, + ) + + with pytest.raises(ValueError) as exc_info: + await kb_mgr.update_kb( + kb_id=mock_knowledge_base.kb_id, + kb_name="updated_kb", + embedding_provider_id="new-embedding-provider", + ) + + assert "知识库索引维度与当前嵌入模型维度不一致" in str(exc_info.value) + assert kb_mgr.kb_insts[mock_knowledge_base.kb_id] is old_helper + assert old_helper.kb.kb_name == "test_kb" + assert old_helper.kb.embedding_provider_id == "test-embedding-provider" @pytest.mark.asyncio @@ -163,11 +201,9 @@ async def test_update_kb_switches_instance_only_after_new_reinit_success( Test that update_kb only switches to the new KBHelper instance after the new instance successfully initializes. """ - # Lazy import to avoid circular import from astrbot.core.knowledge_base.kb_helper import KBHelper from astrbot.core.knowledge_base.kb_mgr import KnowledgeBaseManager - # Setup: create an existing KBHelper mock_provider_manager.get_provider_by_id.return_value = mock_embedding_provider old_helper = KBHelper.__new__(KBHelper) @@ -186,7 +222,6 @@ async def test_update_kb_switches_instance_only_after_new_reinit_success( kb_mgr.kb_insts = {mock_knowledge_base.kb_id: old_helper} kb_mgr.retrieval_manager = MagicMock() - # Mock session context for database operations mock_session = MagicMock() mock_session.add = MagicMock() mock_session.commit = AsyncMock() @@ -197,25 +232,20 @@ async def test_update_kb_switches_instance_only_after_new_reinit_success( mock_db_context.__aexit__ = AsyncMock() mock_kb_db.get_db.return_value = mock_db_context - # Mock KBHelper.initialize to succeed with patch.object(KBHelper, "initialize", new_callable=AsyncMock) as mock_init: mock_init.return_value = None - # Execute update_kb result = await kb_mgr.update_kb( kb_id=mock_knowledge_base.kb_id, kb_name="updated_kb", embedding_provider_id="new-embedding-provider", ) - # Verify: a new helper should be returned - assert result is not None - assert result is not old_helper - assert result.init_error is None - assert kb_mgr.kb_insts[mock_knowledge_base.kb_id] is result - - # Verify: old helper should be terminated - old_helper.terminate.assert_called_once() + assert result is not None + assert result is not old_helper + assert result.init_error is None + assert kb_mgr.kb_insts[mock_knowledge_base.kb_id] is result + old_helper.terminate.assert_called_once() @pytest.mark.asyncio @@ -230,10 +260,8 @@ async def test_ensure_vec_db_clears_stale_init_error( Test that _ensure_vec_db clears the init_error attribute after successful initialization, removing stale error state. """ - # Lazy import to avoid circular import from astrbot.core.knowledge_base.kb_helper import KBHelper - # Setup: create KBHelper with stale init_error mock_provider_manager.get_provider_by_id.return_value = mock_embedding_provider helper = KBHelper.__new__(KBHelper) @@ -247,7 +275,6 @@ async def test_ensure_vec_db_clears_stale_init_error( helper.kb_medias_dir = helper.kb_dir / "medias" / mock_knowledge_base.kb_id helper.kb_files_dir = helper.kb_dir / "files" / mock_knowledge_base.kb_id - # Mock FaissVecDB initialization mock_vec_db = MagicMock() mock_vec_db.initialize = AsyncMock() mock_vec_db.close = AsyncMock() @@ -256,12 +283,10 @@ async def test_ensure_vec_db_clears_stale_init_error( "astrbot.core.db.vec_db.faiss_impl.vec_db.FaissVecDB", return_value=mock_vec_db, ): - # Execute _ensure_vec_db await helper._ensure_vec_db() - # Verify: init_error should be cleared - assert helper.init_error is None - assert helper.vec_db is mock_vec_db + assert helper.init_error is None + assert helper.vec_db is mock_vec_db @pytest.mark.asyncio @@ -275,10 +300,8 @@ async def test_ensure_vec_db_sets_init_error_on_failure( Test that _ensure_vec_db does NOT clear init_error when initialization fails, preserving the error state. """ - # Lazy import to avoid circular import from astrbot.core.knowledge_base.kb_helper import KBHelper - # Setup: provider unavailable mock_provider_manager.get_provider_by_id.return_value = None helper = KBHelper.__new__(KBHelper) @@ -292,14 +315,9 @@ async def test_ensure_vec_db_sets_init_error_on_failure( helper.kb_medias_dir = helper.kb_dir / "medias" / mock_knowledge_base.kb_id helper.kb_files_dir = helper.kb_dir / "files" / mock_knowledge_base.kb_id - # Execute _ensure_vec_db - should raise exception try: await helper._ensure_vec_db() pytest.fail("Expected exception but none was raised") except ValueError as e: - # Verify: exception should be raised assert "无法找到" in str(e) or "未配置" in str(e) - - # Verify: init_error should NOT be cleared (still has previous error) - # Note: _ensure_vec_db doesn't set init_error; that's done by the caller assert helper.init_error is not None From bbc3f678ec8f3aae166c8b2a4876449d56cffde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=E2=82=82=E2=82=82H=E2=82=82=E2=82=85NO=E2=82=86?= <96930391+Sisyphbaous-DT-Project@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:15:10 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(kb):=20=E4=BF=9D=E7=95=99=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=A4=B1=E8=B4=A5=E7=9A=84=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/knowledge_base/kb_mgr.py | 2 +- tests/unit/test_kb_manager_resilience.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/astrbot/core/knowledge_base/kb_mgr.py b/astrbot/core/knowledge_base/kb_mgr.py index 81fc90e764..4251c2f341 100644 --- a/astrbot/core/knowledge_base/kb_mgr.py +++ b/astrbot/core/knowledge_base/kb_mgr.py @@ -249,7 +249,7 @@ def rollback_state() -> None: f"知识库 {kb.kb_name}({kb.kb_id}) 重新初始化失败,继续使用旧实例: {e}", exc_info=True, ) - raise ValueError(str(e)) from e + raise except Exception as e: # Roll back in-memory settings and keep current helper available. rollback_state() diff --git a/tests/unit/test_kb_manager_resilience.py b/tests/unit/test_kb_manager_resilience.py index 2f4ab5b729..d4b862df4f 100644 --- a/tests/unit/test_kb_manager_resilience.py +++ b/tests/unit/test_kb_manager_resilience.py @@ -124,7 +124,7 @@ async def test_update_kb_raises_error_and_preserves_old_instance_when_reinit_fai with patch.object(KBHelper, "initialize", new_callable=AsyncMock) as mock_init: mock_init.side_effect = Exception("Embedding provider unavailable") - with pytest.raises(ValueError) as exc_info: + with pytest.raises(KnowledgeBaseUploadError) as exc_info: await kb_mgr.update_kb( kb_id=mock_knowledge_base.kb_id, kb_name="updated_kb", @@ -183,6 +183,7 @@ async def test_update_kb_raises_user_facing_error_for_dimension_mismatch( embedding_provider_id="new-embedding-provider", ) + assert exc_info.value.stage == "embedding" assert "知识库索引维度与当前嵌入模型维度不一致" in str(exc_info.value) assert kb_mgr.kb_insts[mock_knowledge_base.kb_id] is old_helper assert old_helper.kb.kb_name == "test_kb" From 4bffb3a5972bd51d450936d067d977743a19e0ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=E2=82=82=E2=82=82H=E2=82=82=E2=82=85NO=E2=82=86?= <96930391+Sisyphbaous-DT-Project@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:19:57 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(tests):=20=E4=BF=AE=E6=AD=A3=E7=9F=A5?= =?UTF-8?q?=E8=AF=86=E5=BA=93=E6=9B=B4=E6=96=B0=E5=A4=B1=E8=B4=A5=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/test_kb_manager_resilience.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_kb_manager_resilience.py b/tests/unit/test_kb_manager_resilience.py index d4b862df4f..adf3a1b3f0 100644 --- a/tests/unit/test_kb_manager_resilience.py +++ b/tests/unit/test_kb_manager_resilience.py @@ -124,7 +124,7 @@ async def test_update_kb_raises_error_and_preserves_old_instance_when_reinit_fai with patch.object(KBHelper, "initialize", new_callable=AsyncMock) as mock_init: mock_init.side_effect = Exception("Embedding provider unavailable") - with pytest.raises(KnowledgeBaseUploadError) as exc_info: + with pytest.raises(ValueError) as exc_info: await kb_mgr.update_kb( kb_id=mock_knowledge_base.kb_id, kb_name="updated_kb", @@ -176,7 +176,7 @@ async def test_update_kb_raises_user_facing_error_for_dimension_mismatch( details={"index_dimension": 768, "provider_dimension": 1536}, ) - with pytest.raises(ValueError) as exc_info: + with pytest.raises(KnowledgeBaseUploadError) as exc_info: await kb_mgr.update_kb( kb_id=mock_knowledge_base.kb_id, kb_name="updated_kb",