Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b453a72
feat: add category column to variable model and validation logic
ogabrielluiz Mar 14, 2025
dcf8d0c
feat: add LLM loading and utility functions
ogabrielluiz Mar 14, 2025
ee37cc8
feat: enhance variable management with category support and LLM settings
ogabrielluiz Mar 14, 2025
b1b862a
feat: implement async LLM generation and loading in Component
ogabrielluiz Mar 14, 2025
5699903
test: add unit and integration tests for LLM loading functionality
ogabrielluiz Mar 14, 2025
a9b0d26
feat: add hook to retrieve variables by category and enhance global v…
ogabrielluiz Mar 14, 2025
4890a45
feat: add LLM settings page and integrate into settings navigation
ogabrielluiz Mar 14, 2025
f183436
feat: enhance validation for LLM settings in DatabaseVariableService
ogabrielluiz Mar 14, 2025
2dda874
feat: add async method to retrieve global LLM instance
ogabrielluiz Mar 14, 2025
205f9b1
feat: add nullable category column to variable table in migration script
ogabrielluiz Apr 7, 2025
0fcb3d8
fix: update down_revision in migration script for category column add…
ogabrielluiz Apr 16, 2025
9fc688c
Merge branch 'main' into global-llm
ogabrielluiz Sep 17, 2025
7408646
refactor: remove unused LLM attribute from Component class
ogabrielluiz Sep 17, 2025
0dddf96
feat: add abstract method for retrieving variables by category
ogabrielluiz Sep 17, 2025
320f8ad
feat: add CATEGORY_KB to valid categories in constants
ogabrielluiz Sep 17, 2025
30effce
feat: add non-nullable category column to variable table
ogabrielluiz Sep 17, 2025
9ff9226
chore: clean up package-lock.json by removing deprecated and unused d…
ogabrielluiz Sep 17, 2025
a9a9e02
feat: replace LLMSettingsPage with KBSettingsPage for Knowledge Base …
ogabrielluiz Sep 17, 2025
7825f52
feat: implement vector store factory for Knowledge Bases
ogabrielluiz Sep 17, 2025
6c19fa1
feat: add provider-aware metadata adapters for Knowledge Bases
ogabrielluiz Sep 17, 2025
45a6cfa
feat: enhance metadata extraction for Knowledge Bases with user-aware…
ogabrielluiz Sep 17, 2025
6a42bc5
feat: refactor vector store integration for Knowledge Bases
ogabrielluiz Sep 17, 2025
ba9a795
feat: add unit tests for knowledge bases functionality
ogabrielluiz Sep 17, 2025
51da64d
feat: add unit tests for knowledge bases functionality
ogabrielluiz Sep 17, 2025
4c4e15e
Merge branch 'main' into global-llm
ogabrielluiz Sep 18, 2025
d33858b
feat: enhance OpenSearch vector store integration with new adapter an…
ogabrielluiz Sep 18, 2025
dd95e47
fix: improve OpenSearch metadata adapter functionality and error hand…
ogabrielluiz Sep 18, 2025
07866f5
refactor: enhance OpenSearch with classmethods
ogabrielluiz Sep 18, 2025
74bf0d2
chore: add blank line for improved readability in test file
ogabrielluiz Sep 18, 2025
de4cdf2
refactor: update OpenSearch vector store tests to use new adapter
ogabrielluiz Sep 18, 2025
0227bd8
refactor: update OpenSearch vector store tests to use real adapter
ogabrielluiz Sep 18, 2025
d62f78e
Merge branch 'main' of https://github.com/langflow-ai/langflow into g…
deon-sanchez Oct 16, 2025
4a658ca
chore: update component index
github-actions[bot] Oct 16, 2025
a8ee772
Merge branch 'main' into global-llm
deon-sanchez Oct 16, 2025
0e605b4
chore: update component index
github-actions[bot] Oct 16, 2025
7739c56
Update src/backend/base/langflow/alembic/versions/e5fc330efa7c_add_ca…
erichare Oct 16, 2025
1bb4bb0
Update src/frontend/src/pages/SettingsPage/KBSettingsPage.tsx
erichare Oct 16, 2025
b65362b
Update src/backend/tests/unit/base/knowledge_bases/test_database_fact…
erichare Oct 16, 2025
7d3fcb2
Update src/frontend/src/pages/SettingsPage/KBSettingsPage.tsx
erichare Oct 16, 2025
11d58c2
chore: update component index
github-actions[bot] Oct 16, 2025
ebaa39d
Merge branch 'main' into global-llm
erichare Oct 16, 2025
c8062cc
Update src/backend/tests/unit/base/knowledge_bases/test_metadata_adap…
erichare Oct 16, 2025
763880d
Update src/backend/tests/unit/base/knowledge_bases/test_database_fact…
erichare Oct 16, 2025
2dcc710
chore: update component index
github-actions[bot] Oct 16, 2025
5b224c7
Update vector_store_factory.py
erichare Oct 16, 2025
db4fd7c
chore: update component index
github-actions[bot] Oct 16, 2025
8b8f97c
Update KBSettingsPage.tsx
erichare Oct 16, 2025
d328c49
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 16, 2025
069d157
Update e5fc330efa7c_add_category_column_in_variable.py
erichare Oct 16, 2025
d6422c5
chore: update component index
github-actions[bot] Oct 16, 2025
99fce7f
Update e5fc330efa7c_add_category_column_in_variable.py
erichare Oct 16, 2025
86c7b8b
chore: update component index
github-actions[bot] Oct 16, 2025
54f8f19
Merge branch 'main' into global-llm
erichare Oct 16, 2025
0734ced
chore: update component index
github-actions[bot] Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add unit tests for knowledge bases functionality
Introduced new unit tests for knowledge bases, including tests for database-driven vector store factory, enhanced metadata adapters, and KB variable service integration. These tests improve coverage and ensure robust functionality across various components, enhancing the reliability of knowledge base management. The additions include tests for creating and retrieving metadata, handling different vector store providers, and validating KB variable structures.
  • Loading branch information
ogabrielluiz committed Sep 17, 2025
commit ba9a795d595f0f7ed58b476f6ded2562d8cae947
1 change: 1 addition & 0 deletions src/backend/tests/unit/base/knowledge_bases/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for knowledge bases functionality."""
340 changes: 340 additions & 0 deletions src/backend/tests/unit/base/knowledge_bases/test_database_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
"""Tests for database-driven vector store factory."""

import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch
from uuid import uuid4

import pytest
from langflow.base.knowledge_bases.vector_store_factory import (
ChromaVectorStoreAdapter,
MockOpenSearchVectorStore,
build_kb_vector_store,
)


class TestVectorStoreFactory:
"""Test the database-driven vector store factory."""

@pytest.fixture
def mock_session(self):
"""Mock database session."""
return AsyncMock()

@pytest.fixture
def user_id(self):
"""Test user ID."""
return uuid4()

@pytest.fixture
def kb_path(self):
"""Test knowledge base path."""
with tempfile.TemporaryDirectory() as temp_dir:
yield Path(temp_dir)

@pytest.fixture
def mock_variable_service(self):
"""Mock variable service."""
return AsyncMock()

@pytest.mark.asyncio
@patch("langflow.base.knowledge_bases.vector_store_factory.get_variable_service")
async def test_build_chroma_store_default(
self, mock_get_service, mock_variable_service, mock_session, user_id, kb_path
):
"""Test building Chroma store with default configuration (no variables)."""
# Setup mocks
mock_get_service.return_value = mock_variable_service
mock_variable_service.get_by_category.return_value = [] # No KB variables

with patch("langchain_chroma.Chroma") as mock_chroma:
mock_chroma_instance = Mock()
mock_chroma.return_value = mock_chroma_instance

Comment on lines +51 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Patch the symbol at the import site (factory), not the library module.

Patching langchain_chroma.Chroma won’t affect the symbol already imported in the factory. Patch Chroma where it’s used: langflow.base.knowledge_bases.vector_store_factory.Chroma.

Apply this diff:

-        with patch("langchain_chroma.Chroma") as mock_chroma:
+        with patch("langflow.base.knowledge_bases.vector_store_factory.Chroma") as mock_chroma:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with patch("langchain_chroma.Chroma") as mock_chroma:
mock_chroma_instance = Mock()
mock_chroma.return_value = mock_chroma_instance
with patch("langflow.base.knowledge_bases.vector_store_factory.Chroma") as mock_chroma:
mock_chroma_instance = Mock()
mock_chroma.return_value = mock_chroma_instance
🤖 Prompt for AI Agents
In src/backend/tests/unit/base/knowledge_bases/test_database_factory.py around
lines 51 to 54, the test patches langchain_chroma.Chroma which doesn't replace
the Chroma symbol already imported into the factory; update the patch target to
the import site used by the factory (patch
"langflow.base.knowledge_bases.vector_store_factory.Chroma") so the factory's
Chroma is mocked; keep the rest of the mock setup the same and ensure any
startswith/return_value usage targets the patched object at that import path.

# Call factory
result = await build_kb_vector_store(
kb_path=kb_path,
collection_name="test_collection",
embedding_function=None,
user_id=user_id,
session=mock_session,
)

# Verify result
assert isinstance(result, ChromaVectorStoreAdapter)
assert result._store == mock_chroma_instance

# Verify Chroma was called with correct parameters
mock_chroma.assert_called_once_with(
persist_directory=str(kb_path),
collection_name="test_collection",
embedding_function=None,
client_settings=None,
)

@pytest.mark.asyncio
@patch("langflow.base.knowledge_bases.vector_store_factory.get_variable_service")
async def test_build_opensearch_store(
self, mock_get_service, mock_variable_service, mock_session, user_id, kb_path
):
"""Test building OpenSearch store with configuration."""
# Setup KB variables for OpenSearch
mock_var1 = Mock()
mock_var1.name = "kb_provider"
mock_var1.value = "opensearch"

mock_var2 = Mock()
mock_var2.name = "kb_opensearch_url"
mock_var2.value = "https://localhost:9200"

mock_var3 = Mock()
mock_var3.name = "kb_opensearch_index_prefix"
mock_var3.value = "test-"

mock_variables = [mock_var1, mock_var2, mock_var3]

mock_get_service.return_value = mock_variable_service
mock_variable_service.get_by_category.return_value = mock_variables

# Call factory
result = await build_kb_vector_store(
kb_path=kb_path,
collection_name="test_collection",
embedding_function=None,
user_id=user_id,
session=mock_session,
)

# Verify result
assert isinstance(result, MockOpenSearchVectorStore)
assert result.opensearch_url == "https://localhost:9200"
assert result.index_name == "test-test_collection"

@pytest.mark.asyncio
@patch("langflow.base.knowledge_bases.vector_store_factory.get_variable_service")
async def test_build_chroma_with_server_config(
self, mock_get_service, mock_variable_service, mock_session, user_id, kb_path
):
"""Test building Chroma store with server configuration."""
# Setup KB variables for Chroma server
mock_var1 = Mock()
mock_var1.name = "kb_provider"
mock_var1.value = "chroma"

mock_var2 = Mock()
mock_var2.name = "kb_chroma_server_host"
mock_var2.value = "localhost"

mock_var3 = Mock()
mock_var3.name = "kb_chroma_server_http_port"
mock_var3.value = "8000"

mock_var4 = Mock()
mock_var4.name = "kb_chroma_server_ssl_enabled"
mock_var4.value = "true"

mock_variables = [mock_var1, mock_var2, mock_var3, mock_var4]

mock_get_service.return_value = mock_variable_service
mock_variable_service.get_by_category.return_value = mock_variables

with (
patch("langchain_chroma.Chroma") as mock_chroma,
patch("chromadb.HttpClient") as mock_http_client,
):
mock_chroma_instance = Mock()
mock_chroma.return_value = mock_chroma_instance
mock_client_instance = Mock()
mock_http_client.return_value = mock_client_instance

# Call factory
result = await build_kb_vector_store(
kb_path=kb_path,
collection_name="test_collection",
embedding_function=None,
user_id=user_id,
session=mock_session,
)

# Verify result
assert isinstance(result, ChromaVectorStoreAdapter)

# Verify HttpClient was created with correct parameters
mock_http_client.assert_called_once_with(
host="localhost",
port=8000,
ssl=True,
)

# Verify Chroma was called with client
mock_chroma.assert_called_once_with(
client=mock_client_instance,
collection_name="test_collection",
embedding_function=None,
)

@pytest.mark.asyncio
@patch("langflow.base.knowledge_bases.vector_store_factory.get_variable_service")
async def test_unsupported_provider_raises_error(
self, mock_get_service, mock_variable_service, mock_session, user_id, kb_path
):
"""Test that unsupported provider raises ValueError."""
# Setup KB variables with unsupported provider
mock_var1 = Mock()
mock_var1.name = "kb_provider"
mock_var1.value = "unsupported_provider"

mock_variables = [mock_var1]

mock_get_service.return_value = mock_variable_service
mock_variable_service.get_by_category.return_value = mock_variables

# Call factory and expect error
with pytest.raises(ValueError, match="Unsupported vector store provider: unsupported_provider"):
await build_kb_vector_store(
kb_path=kb_path,
collection_name="test_collection",
embedding_function=None,
user_id=user_id,
session=mock_session,
)

@pytest.mark.asyncio
@patch("langflow.base.knowledge_bases.vector_store_factory.get_variable_service")
async def test_variable_service_called_correctly(
self, mock_get_service, mock_variable_service, mock_session, user_id, kb_path
):
"""Test that variable service is called with correct parameters."""
mock_get_service.return_value = mock_variable_service
mock_variable_service.get_by_category.return_value = []

with patch("langchain_chroma.Chroma"):
await build_kb_vector_store(
kb_path=kb_path,
collection_name="test_collection",
embedding_function=None,
user_id=user_id,
session=mock_session,
)

# Verify variable service was called correctly
mock_get_service.assert_called_once()
mock_variable_service.get_by_category.assert_called_once_with(user_id, "KB", mock_session)


class TestChromaVectorStoreAdapter:
"""Test ChromaVectorStoreAdapter."""

@pytest.fixture
def mock_chroma_store(self):
"""Mock LangChain Chroma store."""
return Mock()

@pytest.fixture
def adapter(self, mock_chroma_store):
"""ChromaVectorStoreAdapter instance."""
return ChromaVectorStoreAdapter(mock_chroma_store)

def test_adapter_delegates_get(self, adapter, mock_chroma_store):
"""Test that adapter delegates get() to underlying store."""
mock_chroma_store.get.return_value = {"documents": ["test"]}

result = adapter.get(include=["documents"])

assert result == {"documents": ["test"]}
mock_chroma_store.get.assert_called_once_with(include=["documents"])

def test_adapter_delegates_add_documents(self, adapter, mock_chroma_store):
"""Test that adapter delegates add_documents() to underlying store."""
mock_documents = [Mock(), Mock()]
mock_chroma_store.add_documents.return_value = ["id1", "id2"]

result = adapter.add_documents(mock_documents)

assert result == ["id1", "id2"]
mock_chroma_store.add_documents.assert_called_once_with(mock_documents)

def test_adapter_delegates_similarity_search(self, adapter, mock_chroma_store):
"""Test that adapter delegates similarity_search() to underlying store."""
mock_results = [Mock(), Mock()]
mock_chroma_store.similarity_search.return_value = mock_results

result = adapter.similarity_search("test query", k=5)

assert result == mock_results
mock_chroma_store.similarity_search.assert_called_once_with("test query", k=5)

def test_adapter_exposes_chroma_attributes(self, adapter, mock_chroma_store):
"""Test that adapter exposes Chroma-specific attributes."""
mock_collection = Mock()
mock_client = Mock()
mock_chroma_store._collection = mock_collection
mock_chroma_store._client = mock_client

assert adapter._collection == mock_collection
assert adapter._client == mock_client


class TestMockOpenSearchVectorStore:
"""Test MockOpenSearchVectorStore."""

@pytest.fixture
def mock_store(self):
"""MockOpenSearchVectorStore instance."""
return MockOpenSearchVectorStore(opensearch_url="https://localhost:9200", index_name="test-index")

def test_mock_store_initialization(self, mock_store):
"""Test mock store initializes correctly."""
assert mock_store.opensearch_url == "https://localhost:9200"
assert mock_store.index_name == "test-index"
assert mock_store._documents == []

def test_mock_store_add_documents(self, mock_store):
"""Test adding documents to mock store."""
mock_doc1 = Mock()
mock_doc1.page_content = "Document 1 content"
mock_doc1.metadata = {"source": "doc1"}

mock_doc2 = Mock()
mock_doc2.page_content = "Document 2 content"
mock_doc2.metadata = {"source": "doc2"}

doc_ids = mock_store.add_documents([mock_doc1, mock_doc2])

assert len(doc_ids) == 2
assert doc_ids == ["doc_0", "doc_1"]
assert len(mock_store._documents) == 2
assert mock_store._documents[0]["content"] == "Document 1 content"
assert mock_store._documents[1]["content"] == "Document 2 content"

def test_mock_store_similarity_search(self, mock_store):
"""Test similarity search on mock store."""
# Add some documents first
mock_doc = Mock()
mock_doc.page_content = "Test content"
mock_doc.metadata = {"source": "test"}
mock_store.add_documents([mock_doc])

results = mock_store.similarity_search("query", k=1)

assert len(results) == 1
# Results should be Data objects
assert hasattr(results[0], "data")

def test_mock_store_get_method(self, mock_store):
"""Test get method on mock store."""
# Add a document first
mock_doc = Mock()
mock_doc.page_content = "Test content"
mock_doc.metadata = {"key": "value"}
mock_store.add_documents([mock_doc])

result = mock_store.get(include=["documents", "metadatas", "ids"])

assert "documents" in result
assert "metadatas" in result
assert "ids" in result
assert len(result["documents"]) == 1
assert result["documents"][0] == "Test content"
assert result["metadatas"][0] == {"key": "value"}
assert result["ids"][0] == "doc_0"
Loading