diff --git a/scripts/migrate/consolidate_bundles.py b/scripts/migrate/consolidate_bundles.py index 86cad18366..dd79030925 100755 --- a/scripts/migrate/consolidate_bundles.py +++ b/scripts/migrate/consolidate_bundles.py @@ -155,6 +155,9 @@ PROVIDER_DEPS: dict[str, list[str]] = { "cuga>=0.2.20,<0.3.0; sys_platform != 'darwin' and python_version < '3.14'", "cuga>=0.2.20,<0.3.0; sys_platform == 'darwin' and platform_machine == 'arm64' and python_version < '3.14'", ], + # --- tranche 9: langwatch evaluator (pure httpx REST; the langwatch SDK extra + # is for the tracing service, not this component) --- + "langwatch": [], } _OPTIONAL_DEPS_HEADER = ( diff --git a/src/backend/tests/unit/components/bundles/langwatch/test_langwatch_component.py b/src/backend/tests/unit/components/bundles/langwatch/test_langwatch_component.py index 2a13044ed8..5f6cbdce05 100644 --- a/src/backend/tests/unit/components/bundles/langwatch/test_langwatch_component.py +++ b/src/backend/tests/unit/components/bundles/langwatch/test_langwatch_component.py @@ -7,9 +7,9 @@ import pytest import respx from httpx import Response from lfx.base.langwatch.utils import get_cached_evaluators -from lfx.components.langwatch.langwatch import LangWatchComponent from lfx.schema.data import Data from lfx.schema.dotdict import dotdict +from lfx_bundles.langwatch.langwatch import LangWatchComponent from tests.base import ComponentTestBaseWithoutClient @@ -94,7 +94,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): """Clear the LRU cache before each test.""" get_cached_evaluators.cache_clear() - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") async def test_set_evaluators_success(self, mock_get, component, mock_evaluators): """Test successful setting of evaluators.""" mock_response = Mock() @@ -106,7 +106,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): component.set_evaluators(endpoint) assert component.evaluators == mock_evaluators - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") async def test_set_evaluators_empty_response(self, mock_get, component): """Test setting evaluators with empty response.""" mock_response = Mock() @@ -196,7 +196,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): # Should set the selected evaluator assert result["evaluator_name"]["value"] == "test_evaluator" - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") @respx.mock async def test_evaluate_success(self, mock_get, component, mock_evaluators): """Test successful evaluation.""" @@ -248,7 +248,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): assert isinstance(result, Data) assert "No evaluator selected" in result.data["error"] - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") @respx.mock async def test_evaluate_evaluator_not_found(self, mock_get, component, mock_evaluators): """Test evaluation with non-existent evaluator.""" @@ -266,7 +266,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): assert isinstance(result, Data) assert "Selected evaluator 'non_existent_evaluator' not found" in result.data["error"] - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") @respx.mock async def test_evaluate_http_error(self, mock_get, component, mock_evaluators): """Test evaluation with HTTP error.""" @@ -290,7 +290,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): assert isinstance(result, Data) assert "Evaluation error" in result.data["error"] - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") @respx.mock async def test_evaluate_with_tracing(self, mock_get, component, mock_evaluators): """Test evaluation with tracing service.""" @@ -333,7 +333,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): assert isinstance(result, Data) assert result.data == expected_response - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") @respx.mock async def test_evaluate_with_contexts_parsing(self, mock_get, component, mock_evaluators): """Test evaluation with contexts parsing.""" @@ -371,7 +371,7 @@ class TestLangWatchComponent(ComponentTestBaseWithoutClient): assert isinstance(result, Data) assert result.data == expected_response - @patch("lfx.components.langwatch.langwatch.httpx.get") + @patch("lfx_bundles.langwatch.langwatch.httpx.get") @respx.mock async def test_evaluate_timeout_handling(self, mock_get, component, mock_evaluators): """Test evaluation with timeout.""" diff --git a/src/bundles/lfx-bundles/pyproject.toml b/src/bundles/lfx-bundles/pyproject.toml index 7609d6c228..9b4a0b0bda 100644 --- a/src/bundles/lfx-bundles/pyproject.toml +++ b/src/bundles/lfx-bundles/pyproject.toml @@ -56,6 +56,7 @@ homeassistant = ["requests>=2.32.0"] huggingface = ["langchain-huggingface~=1.2.0; sys_platform != 'darwin' or platform_machine != 'x86_64'", "huggingface-hub[inference]>=1.0.0,<2.0.0", "langchain-community>=0.4.1,<1.0.0"] icosacomputing = ["requests>=2.32.0"] jigsawstack = ["jigsawstack==0.2.7"] +langwatch = [] litellm = ["langchain-openai>=1.1.6", "openai>=1.68.2,<3.0.0"] lmstudio = ["langchain-openai>=1.1.6", "openai>=1.68.2,<3.0.0", "langchain-nvidia-ai-endpoints~=1.0.0"] maritalk = ["langchain-community>=0.4.1,<1.0.0"] @@ -126,6 +127,7 @@ all = [ "lfx-bundles[huggingface]", "lfx-bundles[icosacomputing]", "lfx-bundles[jigsawstack]", + "lfx-bundles[langwatch]", "lfx-bundles[litellm]", "lfx-bundles[lmstudio]", "lfx-bundles[maritalk]", diff --git a/src/bundles/lfx-bundles/src/lfx_bundles/langwatch/__init__.py b/src/bundles/lfx-bundles/src/lfx_bundles/langwatch/__init__.py new file mode 100644 index 0000000000..dc3149bfbe --- /dev/null +++ b/src/bundles/lfx-bundles/src/lfx_bundles/langwatch/__init__.py @@ -0,0 +1,3 @@ +from .langwatch import LangWatchComponent + +__all__ = ["LangWatchComponent"] diff --git a/src/lfx/src/lfx/components/langwatch/langwatch.py b/src/bundles/lfx-bundles/src/lfx_bundles/langwatch/langwatch.py similarity index 99% rename from src/lfx/src/lfx/components/langwatch/langwatch.py rename to src/bundles/lfx-bundles/src/lfx_bundles/langwatch/langwatch.py index b2b2a14cde..7c0ba5391b 100644 --- a/src/lfx/src/lfx/components/langwatch/langwatch.py +++ b/src/bundles/lfx-bundles/src/lfx_bundles/langwatch/langwatch.py @@ -3,7 +3,6 @@ import os from typing import Any import httpx - from lfx.base.langwatch.utils import get_cached_evaluators from lfx.custom.custom_component.component import Component from lfx.inputs.inputs import MultilineInput diff --git a/src/lfx/src/lfx/_assets/component_index.json b/src/lfx/src/lfx/_assets/component_index.json index 8449982bb3..9a2133399a 100644 --- a/src/lfx/src/lfx/_assets/component_index.json +++ b/src/lfx/src/lfx/_assets/component_index.json @@ -19858,258 +19858,6 @@ } } ], - [ - "langwatch", - { - "LangWatchEvaluator": { - "base_classes": [ - "JSON" - ], - "beta": false, - "conditional_paths": [], - "custom_fields": {}, - "description": "Evaluates various aspects of language models using LangWatch's evaluation endpoints.", - "display_name": "LangWatch Evaluator", - "documentation": "https://docs.langwatch.ai/langevals/documentation/introduction", - "edited": false, - "field_order": [ - "evaluator_name", - "api_key", - "input", - "output", - "expected_output", - "contexts", - "timeout" - ], - "frozen": false, - "icon": "Langwatch", - "legacy": false, - "metadata": { - "code_hash": "6db3bccd5945", - "dependencies": { - "dependencies": [ - { - "name": "httpx", - "version": "0.28.1" - }, - { - "name": "lfx", - "version": null - } - ], - "total_dependencies": 2 - }, - "module": "lfx.components.langwatch.langwatch.LangWatchComponent" - }, - "minimized": false, - "output_types": [], - "outputs": [ - { - "allows_loop": false, - "cache": true, - "display_name": "Evaluation Result", - "group_outputs": false, - "method": "evaluate", - "name": "evaluation_result", - "selected": "JSON", - "tool_mode": true, - "types": [ - "JSON" - ], - "value": "__UNDEFINED__" - } - ], - "pinned": false, - "template": { - "_type": "Component", - "api_key": { - "_input_type": "SecretStrInput", - "advanced": false, - "display_name": "LangWatch API Key", - "dynamic": false, - "info": "Enter your LangWatch API key.", - "input_types": [], - "load_from_db": true, - "name": "api_key", - "override_skip": false, - "password": true, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "code": { - "advanced": true, - "dynamic": true, - "fileTypes": [], - "file_path": "", - "info": "", - "list": false, - "load_from_db": false, - "multiline": true, - "name": "code", - "password": false, - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "type": "code", - "value": "import json\nimport os\nfrom typing import Any\n\nimport httpx\n\nfrom lfx.base.langwatch.utils import get_cached_evaluators\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import MultilineInput\nfrom lfx.io import (\n BoolInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageTextInput,\n NestedDictInput,\n Output,\n SecretStrInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\n\n\nclass LangWatchComponent(Component):\n display_name: str = \"LangWatch Evaluator\"\n description: str = \"Evaluates various aspects of language models using LangWatch's evaluation endpoints.\"\n documentation: str = \"https://docs.langwatch.ai/langevals/documentation/introduction\"\n icon: str = \"Langwatch\"\n name: str = \"LangWatchEvaluator\"\n\n inputs = [\n DropdownInput(\n name=\"evaluator_name\",\n display_name=\"Evaluator Name\",\n options=[],\n required=True,\n info=\"Select an evaluator.\",\n refresh_button=True,\n real_time_refresh=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"LangWatch API Key\",\n required=True,\n info=\"Enter your LangWatch API key.\",\n ),\n MessageTextInput(\n name=\"input\",\n display_name=\"Input\",\n required=False,\n info=\"The input text for evaluation.\",\n ),\n MessageTextInput(\n name=\"output\",\n display_name=\"Output\",\n required=False,\n info=\"The output text for evaluation.\",\n ),\n MessageTextInput(\n name=\"expected_output\",\n display_name=\"Expected Output\",\n required=False,\n info=\"The expected output for evaluation.\",\n ),\n MessageTextInput(\n name=\"contexts\",\n display_name=\"Contexts\",\n required=False,\n info=\"The contexts for evaluation (comma-separated).\",\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The maximum time (in seconds) allowed for the server to respond before timing out.\",\n value=30,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(name=\"evaluation_result\", display_name=\"Evaluation Result\", method=\"evaluate\"),\n ]\n\n def set_evaluators(self, endpoint: str):\n url = f\"{endpoint}/api/evaluations/list\"\n self.evaluators = get_cached_evaluators(url)\n if not self.evaluators or len(self.evaluators) == 0:\n self.status = f\"No evaluators found from {endpoint}\"\n msg = f\"No evaluators found from {endpoint}\"\n raise ValueError(msg)\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n try:\n logger.info(f\"Updating build config. Field name: {field_name}, Field value: {field_value}\")\n\n if field_name is None or field_name == \"evaluator_name\":\n self.evaluators = self.get_evaluators(os.getenv(\"LANGWATCH_ENDPOINT\", \"https://app.langwatch.ai\"))\n build_config[\"evaluator_name\"][\"options\"] = list(self.evaluators.keys())\n\n # Set a default evaluator if none is selected\n if not getattr(self, \"current_evaluator\", None) and self.evaluators:\n self.current_evaluator = next(iter(self.evaluators))\n build_config[\"evaluator_name\"][\"value\"] = self.current_evaluator\n\n # Define default keys that should always be present\n default_keys = [\"code\", \"_type\", \"evaluator_name\", \"api_key\", \"input\", \"output\", \"timeout\"]\n\n if field_value and field_value in self.evaluators and self.current_evaluator != field_value:\n self.current_evaluator = field_value\n evaluator = self.evaluators[field_value]\n\n # Clear previous dynamic inputs\n keys_to_remove = [key for key in build_config if key not in default_keys]\n for key in keys_to_remove:\n del build_config[key]\n\n # Clear component's dynamic attributes\n for attr in list(self.__dict__.keys()):\n if attr not in default_keys and attr not in {\n \"evaluators\",\n \"dynamic_inputs\",\n \"_code\",\n \"current_evaluator\",\n }:\n delattr(self, attr)\n\n # Add new dynamic inputs\n self.dynamic_inputs = self.get_dynamic_inputs(evaluator)\n for name, input_config in self.dynamic_inputs.items():\n build_config[name] = input_config.to_dict()\n\n # Update required fields\n required_fields = {\"api_key\", \"evaluator_name\"}.union(evaluator.get(\"requiredFields\", []))\n for key in build_config:\n if isinstance(build_config[key], dict):\n build_config[key][\"required\"] = key in required_fields\n\n # Validate presence of default keys\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n logger.warning(f\"Missing required keys in build_config: {missing_keys}\")\n # Add missing keys with default values\n for key in missing_keys:\n build_config[key] = {\"value\": None, \"type\": \"str\"}\n\n # Ensure the current_evaluator is always set in the build_config\n build_config[\"evaluator_name\"][\"value\"] = self.current_evaluator\n\n logger.info(f\"Current evaluator set to: {self.current_evaluator}\")\n\n except (KeyError, AttributeError, ValueError) as e:\n self.status = f\"Error updating component: {e!s}\"\n return build_config\n\n def get_dynamic_inputs(self, evaluator: dict[str, Any]):\n try:\n dynamic_inputs = {}\n\n input_fields = [\n field\n for field in evaluator.get(\"requiredFields\", []) + evaluator.get(\"optionalFields\", [])\n if field not in {\"input\", \"output\"}\n ]\n\n for field in input_fields:\n input_params = {\n \"name\": field,\n \"display_name\": field.replace(\"_\", \" \").title(),\n \"required\": field in evaluator.get(\"requiredFields\", []),\n }\n if field == \"contexts\":\n dynamic_inputs[field] = MultilineInput(**input_params, multiline=True)\n else:\n dynamic_inputs[field] = MessageTextInput(**input_params)\n\n settings = evaluator.get(\"settings\", {})\n for setting_name, setting_config in settings.items():\n schema = evaluator.get(\"settings_json_schema\", {}).get(\"properties\", {}).get(setting_name, {})\n\n input_params = {\n \"name\": setting_name,\n \"display_name\": setting_name.replace(\"_\", \" \").title(),\n \"info\": setting_config.get(\"description\", \"\"),\n \"required\": False,\n }\n\n if schema.get(\"type\") == \"object\":\n input_type = NestedDictInput\n input_params[\"value\"] = schema.get(\"default\", setting_config.get(\"default\", {}))\n elif schema.get(\"type\") == \"boolean\":\n input_type = BoolInput\n input_params[\"value\"] = schema.get(\"default\", setting_config.get(\"default\", False))\n elif schema.get(\"type\") == \"number\":\n is_float = isinstance(schema.get(\"default\", setting_config.get(\"default\")), float)\n input_type = FloatInput if is_float else IntInput\n input_params[\"value\"] = schema.get(\"default\", setting_config.get(\"default\", 0))\n elif \"enum\" in schema:\n input_type = DropdownInput\n input_params[\"options\"] = schema[\"enum\"]\n input_params[\"value\"] = schema.get(\"default\", setting_config.get(\"default\"))\n else:\n input_type = MessageTextInput\n default_value = schema.get(\"default\", setting_config.get(\"default\"))\n input_params[\"value\"] = str(default_value) if default_value is not None else \"\"\n\n dynamic_inputs[setting_name] = input_type(**input_params)\n\n except (KeyError, AttributeError, ValueError, TypeError) as e:\n self.status = f\"Error creating dynamic inputs: {e!s}\"\n return {}\n return dynamic_inputs\n\n async def evaluate(self) -> Data:\n if not self.api_key:\n return Data(data={\"error\": \"API key is required\"})\n\n self.set_evaluators(os.getenv(\"LANGWATCH_ENDPOINT\", \"https://app.langwatch.ai\"))\n self.dynamic_inputs = {}\n if getattr(self, \"current_evaluator\", None) is None and self.evaluators:\n self.current_evaluator = next(iter(self.evaluators))\n\n # Prioritize evaluator_name if it exists\n evaluator_name = getattr(self, \"evaluator_name\", None) or self.current_evaluator\n\n if not evaluator_name:\n if self.evaluators:\n evaluator_name = next(iter(self.evaluators))\n await logger.ainfo(f\"No evaluator was selected. Using default: {evaluator_name}\")\n else:\n return Data(\n data={\"error\": \"No evaluator selected and no evaluators available. Please choose an evaluator.\"}\n )\n\n try:\n evaluator = self.evaluators.get(evaluator_name)\n if not evaluator:\n return Data(data={\"error\": f\"Selected evaluator '{evaluator_name}' not found.\"})\n\n await logger.ainfo(f\"Evaluating with evaluator: {evaluator_name}\")\n\n endpoint = f\"/api/evaluations/{evaluator_name}/evaluate\"\n url = f\"{os.getenv('LANGWATCH_ENDPOINT', 'https://app.langwatch.ai')}{endpoint}\"\n\n headers = {\"Content-Type\": \"application/json\", \"X-Auth-Token\": self.api_key}\n\n payload = {\n \"data\": {\n \"input\": self.input,\n \"output\": self.output,\n \"expected_output\": self.expected_output,\n \"contexts\": self.contexts.split(\",\") if self.contexts else [],\n },\n \"settings\": {},\n }\n\n if self._tracing_service:\n tracer = self._tracing_service.get_tracer(\"langwatch\")\n if tracer is not None and hasattr(tracer, \"trace_id\"):\n payload[\"settings\"][\"trace_id\"] = str(tracer.trace_id)\n\n for setting_name in self.dynamic_inputs:\n payload[\"settings\"][setting_name] = getattr(self, setting_name, None)\n\n async with httpx.AsyncClient(timeout=self.timeout) as client:\n response = await client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n result = response.json()\n\n formatted_result = json.dumps(result, indent=2)\n self.status = f\"Evaluation completed successfully. Result:\\n{formatted_result}\"\n return Data(data=result)\n\n except (httpx.RequestError, KeyError, AttributeError, ValueError) as e:\n error_message = f\"Evaluation error: {e!s}\"\n self.status = error_message\n return Data(data={\"error\": error_message})\n" - }, - "contexts": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "Contexts", - "dynamic": false, - "info": "The contexts for evaluation (comma-separated).", - "input_types": [ - "Message" - ], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "contexts", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "evaluator_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Evaluator Name", - "dynamic": false, - "external_options": {}, - "info": "Select an evaluator.", - "name": "evaluator_name", - "options": [], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": true, - "required": true, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "" - }, - "expected_output": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "Expected Output", - "dynamic": false, - "info": "The expected output for evaluation.", - "input_types": [ - "Message" - ], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "expected_output", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "input": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "Input", - "dynamic": false, - "info": "The input text for evaluation.", - "input_types": [ - "Message" - ], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "input", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "output": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "Output", - "dynamic": false, - "info": "The output text for evaluation.", - "input_types": [ - "Message" - ], - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "output", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The maximum time (in seconds) allowed for the server to respond before timing out.", - "list": false, - "list_add_label": "Add More", - "name": "timeout", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": 30 - } - }, - "tool_mode": false - } - } - ], [ "llm_operations", { @@ -32802,9 +32550,9 @@ ] ], "metadata": { - "num_components": 141, - "num_modules": 23 + "num_components": 140, + "num_modules": 22 }, - "sha256": "89d4f6dbd0267d7d6c85f03e4bf5bd4d962aa39407b1864f0cca0d6ce17decba", + "sha256": "733aff4334f3f5751835fe7244f78e70556846522a41a050ceafb82fe91083b3", "version": "1.11.0" } diff --git a/src/lfx/src/lfx/components/langwatch/__init__.py b/src/lfx/src/lfx/components/langwatch/__init__.py index dc3149bfbe..31cf06e0d6 100644 --- a/src/lfx/src/lfx/components/langwatch/__init__.py +++ b/src/lfx/src/lfx/components/langwatch/__init__.py @@ -1,3 +1,22 @@ -from .langwatch import LangWatchComponent +# lfx-bundles-shim +"""Compatibility shim: lfx.components.langwatch moved to lfx-bundles. -__all__ = ["LangWatchComponent"] +This module re-points to the installed bundle distribution. It contains +no component implementations and no third-party dependencies, and is +removed once the deprecation window closes (M4). +""" + +import importlib +import sys + +try: + sys.modules[__name__] = importlib.import_module("lfx_bundles.langwatch") +except ModuleNotFoundError as exc: + if exc.name is not None and (exc.name == "lfx_bundles" or exc.name.startswith("lfx_bundles.")): + msg = ( + "The 'langwatch' components moved to the 'lfx-bundles' distribution. " + "Install it with: pip install lfx-bundles " + "(or 'pip install langflow', which bundles it)." + ) + raise ModuleNotFoundError(msg, name="lfx_bundles") from exc + raise diff --git a/src/lfx/src/lfx/extension/migration/migration_table.json b/src/lfx/src/lfx/extension/migration/migration_table.json index 2e8980224b..072980ec8f 100644 --- a/src/lfx/src/lfx/extension/migration/migration_table.json +++ b/src/lfx/src/lfx/extension/migration/migration_table.json @@ -4560,6 +4560,26 @@ "legacy_slot": "ext:cuga:CugaComponent@official-pre-a", "target": "ext:cuga:CugaComponent@official", "added_in": "1.11.0" + }, + { + "bare_class_name": "LangWatchComponent", + "target": "ext:langwatch:LangWatchComponent@official", + "added_in": "1.11.0" + }, + { + "import_path": "lfx.components.langwatch.langwatch.LangWatchComponent", + "target": "ext:langwatch:LangWatchComponent@official", + "added_in": "1.11.0" + }, + { + "import_path": "lfx.components.langwatch.LangWatchComponent", + "target": "ext:langwatch:LangWatchComponent@official", + "added_in": "1.11.0" + }, + { + "legacy_slot": "ext:langwatch:LangWatchComponent@official-pre-a", + "target": "ext:langwatch:LangWatchComponent@official", + "added_in": "1.11.0" } ], "ambiguous_bare_names": [ diff --git a/uv.lock b/uv.lock index d87f90c950..c341e18fd0 100644 --- a/uv.lock +++ b/uv.lock @@ -9546,6 +9546,7 @@ requires-dist = [ { name = "lfx-bundles", extras = ["huggingface"], marker = "extra == 'all'", editable = "src/bundles/lfx-bundles" }, { name = "lfx-bundles", extras = ["icosacomputing"], marker = "extra == 'all'", editable = "src/bundles/lfx-bundles" }, { name = "lfx-bundles", extras = ["jigsawstack"], marker = "extra == 'all'", editable = "src/bundles/lfx-bundles" }, + { name = "lfx-bundles", extras = ["langwatch"], marker = "extra == 'all'", editable = "src/bundles/lfx-bundles" }, { name = "lfx-bundles", extras = ["litellm"], marker = "extra == 'all'", editable = "src/bundles/lfx-bundles" }, { name = "lfx-bundles", extras = ["lmstudio"], marker = "extra == 'all'", editable = "src/bundles/lfx-bundles" }, { name = "lfx-bundles", extras = ["maritalk"], marker = "extra == 'all'", editable = "src/bundles/lfx-bundles" }, @@ -9623,7 +9624,7 @@ requires-dist = [ { name = "youtube-transcript-api", marker = "extra == 'youtube'", specifier = ">=1.0.0,<2.0.0" }, { name = "zep-python", marker = "extra == 'zep'", specifier = "==2.0.2" }, ] -provides-extras = ["agentql", "aiml", "altk", "apify", "assemblyai", "azure", "baidu", "bing", "chroma", "cleanlab", "clickhouse", "cloudflare", "codeagents", "cometapi", "composio", "confluence", "couchbase", "cuga", "deepseek", "elastic", "exa", "firecrawl", "git", "glean", "google", "groq", "homeassistant", "huggingface", "icosacomputing", "jigsawstack", "litellm", "lmstudio", "maritalk", "mem0", "milvus", "mistral", "mongodb", "needle", "novita", "nvidia", "olivya", "ollama", "openrouter", "perplexity", "pgvector", "pinecone", "qdrant", "redis", "sambanova", "scrapegraph", "searchapi", "serpapi", "spider", "supabase", "tavily", "toolguard", "twelvelabs", "unstructured", "upstash", "vectara", "vertexai", "vllm", "weaviate", "wikipedia", "wolframalpha", "xai", "yahoosearch", "youtube", "zep", "all"] +provides-extras = ["agentql", "aiml", "altk", "apify", "assemblyai", "azure", "baidu", "bing", "chroma", "cleanlab", "clickhouse", "cloudflare", "codeagents", "cometapi", "composio", "confluence", "couchbase", "cuga", "deepseek", "elastic", "exa", "firecrawl", "git", "glean", "google", "groq", "homeassistant", "huggingface", "icosacomputing", "jigsawstack", "langwatch", "litellm", "lmstudio", "maritalk", "mem0", "milvus", "mistral", "mongodb", "needle", "novita", "nvidia", "olivya", "ollama", "openrouter", "perplexity", "pgvector", "pinecone", "qdrant", "redis", "sambanova", "scrapegraph", "searchapi", "serpapi", "spider", "supabase", "tavily", "toolguard", "twelvelabs", "unstructured", "upstash", "vectara", "vertexai", "vllm", "weaviate", "wikipedia", "wolframalpha", "xai", "yahoosearch", "youtube", "zep", "all"] [[package]] name = "lfx-cohere"