Source code for haive.core.utils.haive_discovery.tool_analyzers
"""Analyzers for tool-related components."""
import importlib
import inspect
import logging
from datetime import datetime
from typing import Any
from haive.core.utils.haive_discovery.base_analyzer import ComponentAnalyzer
from haive.core.utils.haive_discovery.component_info import ComponentInfo
logger = logging.getLogger(__name__)
# Check for LangChain availability
try:
from langchain_core.tools import BaseTool, StructuredTool
from pydantic import create_model
LANGCHAIN_AVAILABLE = True
except ImportError:
logger.warning("LangChain not available. Tool features will be limited.")
LANGCHAIN_AVAILABLE = False
[docs]
class ToolAnalyzer(ComponentAnalyzer):
"""Analyzer for LangChain tools."""
[docs]
def can_analyze(self, obj: Any) -> bool:
if not LANGCHAIN_AVAILABLE:
return False
try:
return isinstance(obj, BaseTool)
except BaseException:
return False
[docs]
def analyze(self, obj: Any, module_path: str) -> ComponentInfo:
return ComponentInfo(
name=self.safe_get_name(obj, "Tool"),
component_type="tool",
module_path=module_path,
class_name=self.safe_get_class_name(obj),
description=getattr(obj, "description", "") or "",
source_code=self.get_source_code(obj),
env_vars=self.detect_env_vars(self.get_source_code(obj)),
schema=self.extract_schema(obj),
metadata=getattr(obj, "metadata", {}) or {},
timestamp=datetime.now().isoformat(),
tool_instance=obj,
)
[docs]
class DocumentLoaderAnalyzer(ComponentAnalyzer):
"""Analyzer for document loaders."""
[docs]
def can_analyze(self, obj: Any) -> bool:
return (
inspect.isclass(obj)
and hasattr(obj, "load")
and callable(getattr(obj, "load", None))
)
[docs]
def analyze(self, obj: Any, module_path: str) -> ComponentInfo:
info = ComponentInfo(
name=self.safe_get_name(obj, "DocumentLoader"),
component_type="document_loader",
module_path=module_path,
class_name=self.safe_get_class_name(obj),
description=inspect.getdoc(obj) or "",
source_code=self.get_source_code(obj),
env_vars=self.detect_env_vars(self.get_source_code(obj)),
schema=self.extract_schema(obj),
metadata={},
timestamp=datetime.now().isoformat(),
)
if LANGCHAIN_AVAILABLE:
info.tool_instance = self.create_tool(info)
return info
[docs]
def create_tool(self, component_info: ComponentInfo) -> Any | None:
"""Convert document loader to a StructuredTool."""
if not LANGCHAIN_AVAILABLE:
return None
try:
# Import the loader class
try:
module = importlib.import_module(component_info.module_path)
loader_class = getattr(module, component_info.class_name)
except (ImportError, AttributeError, SystemExit) as e:
logger.debug(f"Could not import {component_info.class_name}: {e}")
return None
# Create args model
try:
args_model = self.create_pydantic_model(
loader_class, force_serializable=True
)
except Exception as e:
logger.debug(f"Could not create args model: {e}")
args_model = create_model(f"{component_info.class_name}Args")
def loader_function(**kwargs) -> dict[str, Any]:
"""Load documents using the loader."""
try:
# Filter kwargs for valid parameters
filtered_kwargs = {}
if hasattr(loader_class, "__init__"):
try:
sig = inspect.signature(loader_class.__init__)
valid_params = set(sig.parameters.keys()) - {"self"}
for k, v in kwargs.items():
if k in valid_params and v is not None:
filtered_kwargs[k] = v
except BaseException:
pass
# Create instance
try:
instance = loader_class(**filtered_kwargs)
except Exception as e:
try:
instance = loader_class()
except Exception as e2:
return {
"error": f"Could not create instance: {e}",
"fallback_error": str(e2),
"success": False,
}
# Load documents
try:
documents = instance.load()
return {
"success": True,
"num_documents": len(documents),
"total_chars": sum(
len(doc.page_content) for doc in documents
),
"sample_content": (
documents[0].page_content[:200] + "..."
if documents
else ""
),
"documents": [
{
"content": doc.page_content[:500],
"metadata": doc.metadata,
}
for doc in documents[:3]
],
}
except Exception as e:
return {
"error": f"Could not load documents: {e}",
"success": False,
}
except Exception as e:
return {"error": f"Unexpected error: {e}", "success": False}
# Create safe tool name
tool_name = f"load_documents_{component_info.name.lower().replace(' ', '_').replace('-', '_')}"
tool_name = "".join(
c if c.isalnum() or c == "_" else "_" for c in tool_name
)
# Create the tool
return StructuredTool.from_function(
func=loader_function,
name=tool_name,
description=f"Load documents using {component_info.class_name}: { component_info.description[:100] }",
args_schema=args_model,
)
except Exception as e:
logger.debug(f"Error creating tool for {component_info.name}: {e}")
return None