Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# @Time : 2025/12/9 22:59
# @Author : pengqingsong.pqs
# @Email : [email protected]
# @FileName: __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: 'langchain_github_reader'
description: 'Use langchain github reader to read github content'

loader_class: 'GitHubIssuesLoader'
loader_module: 'langchain_community.document_loaders.github'
loader_params:
repo: '' # Repository name (e.g., owner/repo)
access_token: '' # GitHub access token
# Optional parameters
include_prs: true # If True include Pull Requests in results, otherwise ignore them.
milestone: null # If integer is passed, it should be a milestone's number field. If '*' is passed, issues with any milestone are accepted. If 'none' is passed, issues without milestones are returned.
state: 'all' # Filter on issue state. Can be one of: 'open', 'closed', 'all'.
assignee: null # Filter on assigned user. Pass 'none' for no user and '*' for any user.
creator: null # Filter on the user that created the issue.
mentioned: null # Filter on a user that's mentioned in the issue.
labels: null # Label names to filter one. Example: bug,ui,@high.
sort: null # What to sort results by. Can be one of: 'created', 'updated', 'comments'. Default is 'created'.
direction: null # The direction to sort the results by. Can be one of: 'asc', 'desc'.
since: null # Only show notifications updated after the given time. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.
page: null # The page number for paginated results. Defaults to 1 in the GitHub API.
per_page: null # Number of items per page. Defaults to 30 in the GitHub API.

metadata:
type: 'READER'
module: 'agentuniverse.agent.action.knowledge.reader.langchain_bridge.langchain_bridge_reader'
class: 'LangchainBridgeReader'
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: 'langchain_yuque_reader'
description: 'Use langchain yuque reader to read yuque document'
loader_class: 'YuqueLoader'
loader_module: 'langchain_community.document_loaders.yuque'
loader_params:
access_token: '' # Yuque access token

metadata:
type: 'READER'
module: 'agentuniverse.agent.action.knowledge.reader.langchain_bridge.langchain_bridge_reader'
class: 'LangchainBridgeReader'

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# @Time : 2025/12/9 22:59
# @Author : pengqingsong.pqs
# @Email : [email protected]
# @FileName: langchain_bridge_reader.py

import importlib
from typing import List, Any, Dict, Optional

from agentuniverse.agent.action.knowledge.reader.reader import Reader
from agentuniverse.agent.action.knowledge.store.document import Document
from agentuniverse.base.config.component_configer.component_configer import ComponentConfiger


class LangchainBridgeReader(Reader):
"""Generic reader that wraps langchain_community.document_loaders.

This reader can dynamically load and use any loader from langchain_community.document_loaders
based on configuration.

Attributes:
loader_class (str): The class name of the langchain loader (e.g., "TextLoader")
loader_module (str): The module path of the loader (e.g., "langchain_community.document_loaders.text")
loader_params (Dict): Parameters to pass to the loader constructor
"""

loader_class: Optional[str] = None
loader_module: Optional[str] = None
loader_params: Optional[Dict] = None

def _load_data(self, *args: Any, **kwargs: Any) -> List[Document]:
"""Load data using the configured langchain loader.

Args:
*args: Positional arguments to pass to the loader
**kwargs: Keyword arguments to pass to the loader

Returns:
List of Document objects in agentUniverse format
"""
if not self.loader_class or not self.loader_module:
raise ValueError("LangchainReader requires loader_class and loader_module configuration")

try:
# Dynamically import the loader class
module = importlib.import_module(self.loader_module)
loader_cls = getattr(module, self.loader_class)

# Merge constructor parameters
constructor_params = self.loader_params.copy() if self.loader_params else {}
constructor_params.update(kwargs)

# Create loader instance
loader = loader_cls(**constructor_params)

# Load documents using langchain loader
langchain_docs = loader.load()

# Convert to agentUniverse Document format
return Document.from_langchain_list(langchain_docs)

except ImportError as e:
raise ImportError(f"Failed to import loader {self.loader_class} from {self.loader_module}: {e}")
except Exception as e:
raise RuntimeError(f"Error loading documents with {self.loader_class}: {e}")

def _initialize_by_component_configer(self, reader_configer: ComponentConfiger) -> 'Reader':
"""Initialize the reader by the ComponentConfiger object.

Args:
reader_configer(ComponentConfiger): A configer contains reader
basic info.
Returns:
LangchainBridgeReader: A reader instance.
"""
super()._initialize_by_component_configer(reader_configer)
if hasattr(reader_configer, "loader_class"):
self.loader_class = reader_configer.loader_class
if hasattr(reader_configer, "loader_module"):
self.loader_module = reader_configer.loader_module
if hasattr(reader_configer, "loader_params"):
self.loader_params = reader_configer.loader_params
return self
6 changes: 4 additions & 2 deletions agentuniverse/agent/action/knowledge/reader/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def _initialize_by_component_configer(self,
Returns:
Reader: A reader instance.
"""
self.name = reader_configer.name
self.description = reader_configer.description
if hasattr(reader_configer, "name"):
self.name = reader_configer.name
if hasattr(reader_configer, "description"):
self.description = reader_configer.description
return self
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# !/usr/bin/env python3
# -*- coding:utf-8 -*-

# @Time : 2025/12/12 14:25
# @Author : pengqingsong.pqs
# @Email : [email protected]
# @FileName: __init__.py.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# @Time : 2025/12/12 14:25
# @Author : pengqingsong.pqs
# @Email : [email protected]
# @FileName: test_langchain_bridge_reader.py
"""
Unit tests for LangchainBridgeReader
"""

import unittest
from unittest.mock import Mock, patch

from agentuniverse.agent.action.knowledge.reader.langchain_bridge.langchain_bridge_reader import LangchainBridgeReader
from agentuniverse.agent.action.knowledge.store.document import Document


class TestLangchainBridgeReader(unittest.TestCase):
"""Unit tests for LangchainBridgeReader"""

def setUp(self):
"""Set up test fixtures"""
self.mock_langchain_doc = Mock()
self.mock_langchain_doc.page_content = "Test content"
self.mock_langchain_doc.metadata = {"source": "test"}

@patch('importlib.import_module')
@patch('agentuniverse.agent.action.knowledge.reader.langchain_bridge.langchain_bridge_reader.Document.from_langchain_list')
def test_load_data_success(self, mock_from_langchain, mock_import_module):
"""Test successful data loading"""
# Mock the loader class
mock_loader_cls = Mock()
mock_loader_instance = Mock()
mock_loader_instance.load.return_value = [self.mock_langchain_doc]
mock_loader_cls.return_value = mock_loader_instance

mock_module = Mock()
mock_module.TextLoader = mock_loader_cls
mock_import_module.return_value = mock_module

# Mock the conversion
mock_from_langchain.return_value = [Document(text="Test content", metadata={"source": "test"})]

# Test
reader = LangchainBridgeReader(
loader_class="TextLoader",
loader_module="langchain_community.document_loaders.text",
loader_params={
"file_path": "test.txt"
}
)
result = reader.load_data()

# Assertions
self.assertEqual(len(result), 1)
self.assertEqual(result[0].text, "Test content")
mock_loader_cls.assert_called_once_with(file_path="test.txt")

@patch('importlib.import_module')
@patch('agentuniverse.agent.action.knowledge.reader.langchain_bridge.langchain_bridge_reader.Document.from_langchain_list')
def test_load_data_with_kwargs(self, mock_from_langchain, mock_import_module):
"""Test data loading with additional keyword arguments"""
# Mock the loader class
mock_loader_cls = Mock()
mock_loader_instance = Mock()
mock_loader_instance.load.return_value = [self.mock_langchain_doc]
mock_loader_cls.return_value = mock_loader_instance

mock_module = Mock()
mock_module.TextLoader = mock_loader_cls
mock_import_module.return_value = mock_module

# Mock the conversion
mock_from_langchain.return_value = [Document(text="Test content", metadata={"source": "test"})]

# Test with additional kwargs
reader = LangchainBridgeReader(
loader_class="TextLoader",
loader_module="langchain_community.document_loaders.text",
loader_params={
"file_path": "test.txt"
}
)
result = reader.load_data(encoding="utf-8", autodetect_encoding=True)

# Assertions
self.assertEqual(len(result), 1)
mock_loader_cls.assert_called_once_with(file_path="test.txt", encoding="utf-8", autodetect_encoding=True)

def test_missing_loader_configuration(self):
"""Test error handling for missing loader configuration"""
reader = LangchainBridgeReader()

with self.assertRaises(ValueError) as context:
reader.load_data()

self.assertIn("LangchainReader requires loader_class and loader_module configuration", str(context.exception))

@patch('importlib.import_module')
def test_import_error(self, mock_import_module):
"""Test error handling for import failures"""
mock_import_module.side_effect = ImportError("Module not found")

reader = LangchainBridgeReader(
loader_class="NonExistentLoader",
loader_module="non_existent.module"
)

with self.assertRaises(ImportError) as context:
reader.load_data()

self.assertIn("Failed to import loader NonExistentLoader", str(context.exception))

@patch('importlib.import_module')
def test_runtime_error(self, mock_import_module):
"""Test error handling for runtime failures"""
mock_loader_cls = Mock()
mock_loader_instance = Mock()
mock_loader_instance.load.side_effect = Exception("Loader error")
mock_loader_cls.return_value = mock_loader_instance

mock_module = Mock()
mock_module.TestLoader = mock_loader_cls
mock_import_module.return_value = mock_module

reader = LangchainBridgeReader(
loader_class="TestLoader",
loader_module="test.module"
)

with self.assertRaises(RuntimeError) as context:
reader.load_data()

self.assertIn("Error loading documents with TestLoader", str(context.exception))

def test_initialization_with_data(self):
"""Test initialization with data parameters"""
reader = LangchainBridgeReader(
loader_class="TextLoader",
loader_module="langchain_community.document_loaders.text",
loader_params={"file_path": "test.txt"}
)

self.assertEqual(reader.loader_class, "TextLoader")
self.assertEqual(reader.loader_module, "langchain_community.document_loaders.text")
self.assertEqual(reader.loader_params["file_path"], "test.txt")

@patch('importlib.import_module')
@patch('agentuniverse.agent.action.knowledge.reader.langchain_bridge.langchain_bridge_reader.Document.from_langchain_list')
def test_empty_document_list(self, mock_from_langchain, mock_import_module):
"""Test handling of empty document list"""
# Mock the loader class
mock_loader_cls = Mock()
mock_loader_instance = Mock()
mock_loader_instance.load.return_value = [] # Empty list
mock_loader_cls.return_value = mock_loader_instance

mock_module = Mock()
mock_module.TextLoader = mock_loader_cls
mock_import_module.return_value = mock_module

# Mock the conversion
mock_from_langchain.return_value = []

# Test
reader = LangchainBridgeReader(
loader_class="TextLoader",
loader_module="langchain_community.document_loaders.text",
loader_params={"file_path": "empty.txt"}
)
result = reader.load_data()

# Assertions
self.assertEqual(len(result), 0)
mock_loader_cls.assert_called_once_with(file_path="empty.txt")


if __name__ == "__main__":
unittest.main()
Loading