From c46dcb4decaa756cbeaea51d00e68dba216857c3 Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Mon, 15 Dec 2025 10:38:50 -0800 Subject: [PATCH 1/8] feat: Support instance caching for docling Co-Authored-By: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/lfx/src/lfx/base/data/docling_utils.py | 184 +++++++++----- .../lfx/components/docling/docling_inline.py | 88 +++---- .../unit/base/data/test_docling_utils.py | 231 ++++++++++++++++++ 3 files changed, 399 insertions(+), 104 deletions(-) diff --git a/src/lfx/src/lfx/base/data/docling_utils.py b/src/lfx/src/lfx/base/data/docling_utils.py index c2cddcc22970..2a8ff66d2c7e 100644 --- a/src/lfx/src/lfx/base/data/docling_utils.py +++ b/src/lfx/src/lfx/base/data/docling_utils.py @@ -3,7 +3,7 @@ import sys import traceback from contextlib import suppress -from typing import TYPE_CHECKING +from functools import lru_cache from docling_core.types.doc import DoclingDocument from pydantic import BaseModel, SecretStr, TypeAdapter @@ -12,9 +12,6 @@ from lfx.schema.data import Data from lfx.schema.dataframe import DataFrame -if TYPE_CHECKING: - from langchain_core.language_models.chat_models import BaseChatModel - class DoclingDependencyError(Exception): """Custom exception for missing Docling dependencies.""" @@ -152,6 +149,80 @@ def _deserialize_pydantic_model(data: dict): return adapter.validate_python(data["config"]) +# Global cache for DocumentConverter instances +# This cache persists across multiple runs and thread invocations +@lru_cache(maxsize=4) +def _get_cached_converter( + pipeline: str, + ocr_engine: str, + *, + do_picture_classification: bool, + pic_desc_config_hash: str | None, +): + """Create and cache a DocumentConverter instance based on configuration. + + This function uses LRU caching to maintain DocumentConverter instances in memory, + eliminating the 15-20 minute model loading time on subsequent runs. + + Args: + pipeline: The pipeline type ("standard" or "vlm") + ocr_engine: The OCR engine to use + do_picture_classification: Whether to enable picture classification + pic_desc_config_hash: Hash of the picture description config (for cache key) + + Returns: + A cached or newly created DocumentConverter instance + """ + from docling.datamodel.base_models import InputFormat + from docling.datamodel.pipeline_options import OcrOptions, PdfPipelineOptions, VlmPipelineOptions + from docling.document_converter import DocumentConverter, FormatOption, PdfFormatOption + from docling.models.factories import get_ocr_factory + from docling.pipeline.vlm_pipeline import VlmPipeline + + logger.info(f"Creating DocumentConverter for pipeline={pipeline}, ocr_engine={ocr_engine}") + + # Configure the standard PDF pipeline + def _get_standard_opts() -> PdfPipelineOptions: + pipeline_options = PdfPipelineOptions() + pipeline_options.do_ocr = ocr_engine not in {"", "None"} + if pipeline_options.do_ocr: + ocr_factory = get_ocr_factory( + allow_external_plugins=False, + ) + ocr_options: OcrOptions = ocr_factory.create_options( + kind=ocr_engine, + ) + pipeline_options.ocr_options = ocr_options + + pipeline_options.do_picture_classification = do_picture_classification + + # Note: pic_desc_config_hash is for cache key only + # Actual picture description is handled separately (non-cached path) + _ = pic_desc_config_hash # Mark as intentionally unused + + return pipeline_options + + # Configure the VLM pipeline + def _get_vlm_opts() -> VlmPipelineOptions: + return VlmPipelineOptions() + + if pipeline == "standard": + pdf_format_option = PdfFormatOption( + pipeline_options=_get_standard_opts(), + ) + elif pipeline == "vlm": + pdf_format_option = PdfFormatOption(pipeline_cls=VlmPipeline, pipeline_options=_get_vlm_opts()) + else: + msg = f"Unknown pipeline: {pipeline!r}" + raise ValueError(msg) + + format_options: dict[InputFormat, FormatOption] = { + InputFormat.PDF: pdf_format_option, + InputFormat.IMAGE: pdf_format_option, + } + + return DocumentConverter(format_options=format_options) + def docling_worker( *, file_paths: list[str], @@ -162,7 +233,12 @@ def docling_worker( pic_desc_config: dict | None, pic_desc_prompt: str, ): - """Worker function for processing files with Docling in a separate process.""" + """Worker function for processing files with Docling using threading. + + This function now uses a globally cached DocumentConverter instance, + significantly reducing processing time on subsequent runs from 15-20 minutes + to just seconds. + """ # Signal handling for graceful shutdown shutdown_requested = False @@ -205,12 +281,12 @@ def check_shutdown() -> None: check_shutdown() try: - from docling.datamodel.base_models import ConversionStatus, InputFormat - from docling.datamodel.pipeline_options import OcrOptions, PdfPipelineOptions, VlmPipelineOptions - from docling.document_converter import DocumentConverter, FormatOption, PdfFormatOption - from docling.models.factories import get_ocr_factory - from docling.pipeline.vlm_pipeline import VlmPipeline - from langchain_docling.picture_description import PictureDescriptionLangChainOptions + from docling.datamodel.base_models import ConversionStatus, InputFormat # noqa: F401 + from docling.datamodel.pipeline_options import OcrOptions, PdfPipelineOptions, VlmPipelineOptions # noqa: F401 + from docling.document_converter import DocumentConverter, FormatOption, PdfFormatOption # noqa: F401 + from docling.models.factories import get_ocr_factory # noqa: F401 + from docling.pipeline.vlm_pipeline import VlmPipeline # noqa: F401 + from langchain_docling.picture_description import PictureDescriptionLangChainOptions # noqa: F401 # Check for shutdown after imports check_shutdown() @@ -233,27 +309,34 @@ def check_shutdown() -> None: queue.put({"error": "Worker interrupted during imports", "shutdown": True}) return - # Configure the standard PDF pipeline - def _get_standard_opts() -> PdfPipelineOptions: + # Use cached converter instead of creating new one each time + # This is the key optimization that eliminates 15-20 minute model load times + def _get_converter() -> DocumentConverter: check_shutdown() # Check before heavy operations - pipeline_options = PdfPipelineOptions() - pipeline_options.do_ocr = ocr_engine not in {"", "None"} - if pipeline_options.do_ocr: - ocr_factory = get_ocr_factory( - allow_external_plugins=False, - ) - - ocr_options: OcrOptions = ocr_factory.create_options( - kind=ocr_engine, - ) - pipeline_options.ocr_options = ocr_options - - pipeline_options.do_picture_classification = do_picture_classification - + # For now, we don't support pic_desc_config caching due to serialization complexity + # This is a known limitation that can be addressed in a future enhancement if pic_desc_config: - pic_desc_llm: BaseChatModel = _deserialize_pydantic_model(pic_desc_config) - + logger.warning( + "Picture description with LLM is not yet supported with cached converters. " + "Using non-cached converter for this request." + ) + # Fall back to creating a new converter (old behavior) + from docling.datamodel.base_models import InputFormat + from docling.datamodel.pipeline_options import PdfPipelineOptions + from docling.document_converter import DocumentConverter, FormatOption, PdfFormatOption + from docling.models.factories import get_ocr_factory + from langchain_docling.picture_description import PictureDescriptionLangChainOptions + + pipeline_options = PdfPipelineOptions() + pipeline_options.do_ocr = ocr_engine not in {"", "None"} + if pipeline_options.do_ocr: + ocr_factory = get_ocr_factory(allow_external_plugins=False) + ocr_options = ocr_factory.create_options(kind=ocr_engine) + pipeline_options.ocr_options = ocr_options + + pipeline_options.do_picture_classification = do_picture_classification + pic_desc_llm = _deserialize_pydantic_model(pic_desc_config) logger.info("Docling enabling the picture description stage.") pipeline_options.do_picture_description = True pipeline_options.allow_external_plugins = True @@ -261,33 +344,24 @@ def _get_standard_opts() -> PdfPipelineOptions: llm=pic_desc_llm, prompt=pic_desc_prompt, ) - return pipeline_options - # Configure the VLM pipeline - def _get_vlm_opts() -> VlmPipelineOptions: - check_shutdown() # Check before heavy operations - return VlmPipelineOptions() - - # Configure the main format options and create the DocumentConverter() - def _get_converter() -> DocumentConverter: - check_shutdown() # Check before heavy operations - - if pipeline == "standard": - pdf_format_option = PdfFormatOption( - pipeline_options=_get_standard_opts(), - ) - elif pipeline == "vlm": - pdf_format_option = PdfFormatOption(pipeline_cls=VlmPipeline, pipeline_options=_get_vlm_opts()) - else: - msg = f"Unknown pipeline: {pipeline!r}" - raise ValueError(msg) - - format_options: dict[InputFormat, FormatOption] = { - InputFormat.PDF: pdf_format_option, - InputFormat.IMAGE: pdf_format_option, - } - - return DocumentConverter(format_options=format_options) + pdf_format_option = PdfFormatOption(pipeline_options=pipeline_options) + format_options: dict[InputFormat, FormatOption] = { + InputFormat.PDF: pdf_format_option, + InputFormat.IMAGE: pdf_format_option, + } + return DocumentConverter(format_options=format_options) + + # Use cached converter - this is where the magic happens! + # First run: creates and caches converter (15-20 min) + # Subsequent runs: reuses cached converter (seconds) + pic_desc_config_hash = None # Will be None since we checked above + return _get_cached_converter( + pipeline=pipeline, + ocr_engine=ocr_engine, + do_picture_classification=do_picture_classification, + pic_desc_config_hash=pic_desc_config_hash, + ) try: # Check for shutdown before creating converter (can be slow) diff --git a/src/lfx/src/lfx/components/docling/docling_inline.py b/src/lfx/src/lfx/components/docling/docling_inline.py index ce07c144acc8..4a89adecb208 100644 --- a/src/lfx/src/lfx/components/docling/docling_inline.py +++ b/src/lfx/src/lfx/components/docling/docling_inline.py @@ -1,6 +1,6 @@ +import queue +import threading import time -from multiprocessing import Queue, get_context -from queue import Empty from lfx.base.data import BaseFileComponent from lfx.base.data.docling_utils import _serialize_pydantic_model, docling_worker @@ -92,60 +92,57 @@ class DoclingInlineComponent(BaseFileComponent): *BaseFileComponent.get_base_outputs(), ] - def _wait_for_result_with_process_monitoring(self, queue: Queue, proc, timeout: int = 300): - """Wait for result from queue while monitoring process health. + def _wait_for_result_with_thread_monitoring( + self, result_queue: queue.Queue, thread: threading.Thread, timeout: int = 300 + ): + """Wait for result from queue while monitoring thread health. - Handles cases where process crashes without sending result. + Handles cases where thread crashes without sending result. """ start_time = time.time() while time.time() - start_time < timeout: - # Check if process is still alive - if not proc.is_alive(): - # Process died, try to get any result it might have sent + # Check if thread is still alive + if not thread.is_alive(): + # Thread finished, try to get any result it might have sent try: - result = queue.get_nowait() - except Empty: - # Process died without sending result - msg = f"Worker process crashed unexpectedly without producing result. Exit code: {proc.exitcode}" + result = result_queue.get_nowait() + except queue.Empty: + # Thread finished without sending result + msg = "Worker thread crashed unexpectedly without producing result." raise RuntimeError(msg) from None else: - self.log("Process completed and result retrieved") + self.log("Thread completed and result retrieved") return result # Poll the queue instead of blocking try: - result = queue.get(timeout=1) - except Empty: + result = result_queue.get(timeout=1) + except queue.Empty: # No result yet, continue monitoring continue else: - self.log("Result received from worker process") + self.log("Result received from worker thread") return result # Overall timeout reached - msg = f"Process timed out after {timeout} seconds" + msg = f"Thread timed out after {timeout} seconds" raise TimeoutError(msg) - def _terminate_process_gracefully(self, proc, timeout_terminate: int = 10, timeout_kill: int = 5): - """Terminate process gracefully with escalating signals. + def _stop_thread_gracefully(self, thread: threading.Thread, timeout: int = 10): + """Wait for thread to complete gracefully. - First tries SIGTERM, then SIGKILL if needed. + Note: Python threads cannot be forcefully killed, so we just wait. + The thread should respond to shutdown signals via the queue. """ - if not proc.is_alive(): + if not thread.is_alive(): return - self.log("Attempting graceful process termination with SIGTERM") - proc.terminate() # Send SIGTERM - proc.join(timeout=timeout_terminate) + self.log("Waiting for thread to complete gracefully") + thread.join(timeout=timeout) - if proc.is_alive(): - self.log("Process didn't respond to SIGTERM, using SIGKILL") - proc.kill() # Send SIGKILL - proc.join(timeout=timeout_kill) - - if proc.is_alive(): - self.log("Warning: Process still alive after SIGKILL") + if thread.is_alive(): + self.log("Warning: Thread still alive after timeout") def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[BaseFileComponent.BaseFile]: try: @@ -167,44 +164,37 @@ def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[Bas if self.pic_desc_llm is not None: pic_desc_config = _serialize_pydantic_model(self.pic_desc_llm) - ctx = get_context("spawn") - queue: Queue = ctx.Queue() - proc = ctx.Process( + # Use threading instead of multiprocessing for memory sharing + # This enables the global DocumentConverter cache to work across runs + result_queue: queue.Queue = queue.Queue() + thread = threading.Thread( target=docling_worker, kwargs={ "file_paths": file_paths, - "queue": queue, + "queue": result_queue, "pipeline": self.pipeline, "ocr_engine": self.ocr_engine, "do_picture_classification": self.do_picture_classification, "pic_desc_config": pic_desc_config, "pic_desc_prompt": self.pic_desc_prompt, }, + daemon=False, # Allow thread to complete even if main thread exits ) result = None - proc.start() + thread.start() try: - result = self._wait_for_result_with_process_monitoring(queue, proc, timeout=300) + result = self._wait_for_result_with_thread_monitoring(result_queue, thread, timeout=300) except KeyboardInterrupt: - self.log("Docling process cancelled by user") + self.log("Docling thread cancelled by user") result = [] except Exception as e: self.log(f"Error during processing: {e}") raise finally: - # Improved cleanup with graceful termination - try: - self._terminate_process_gracefully(proc) - finally: - # Always close and cleanup queue resources - try: - queue.close() - queue.join_thread() - except Exception as e: # noqa: BLE001 - # Ignore cleanup errors, but log them - self.log(f"Warning: Error during queue cleanup - {e}") + # Wait for thread to complete gracefully + self._stop_thread_gracefully(thread) # Enhanced error checking with dependency-specific handling if isinstance(result, dict) and "error" in result: diff --git a/src/lfx/tests/unit/base/data/test_docling_utils.py b/src/lfx/tests/unit/base/data/test_docling_utils.py index 2d017931b65e..4c07fe87f9fa 100644 --- a/src/lfx/tests/unit/base/data/test_docling_utils.py +++ b/src/lfx/tests/unit/base/data/test_docling_utils.py @@ -1,5 +1,8 @@ """Tests for docling_utils module.""" +import time +from unittest.mock import MagicMock, patch + import pytest try: @@ -134,3 +137,231 @@ def test_extract_from_none(self): """Test extracting from None raises error.""" with pytest.raises(TypeError, match="No data inputs provided"): extract_docling_documents(None, "doc") + + +class TestDocumentConverterCaching: + """Test DocumentConverter caching functionality.""" + + def test_cached_converter_function_exists(self): + """Test that _get_cached_converter function exists and is properly decorated.""" + from lfx.base.data.docling_utils import _get_cached_converter + + # Verify function exists + assert callable(_get_cached_converter) + + # Verify it has cache_info method (indicates lru_cache decorator) + assert hasattr(_get_cached_converter, "cache_info") + assert callable(_get_cached_converter.cache_info) + + def test_cached_converter_cache_key(self): + """Test that cache uses correct parameters as key.""" + from lfx.base.data.docling_utils import _get_cached_converter + + # Clear cache before test + _get_cached_converter.cache_clear() + + # Mock the DocumentConverter creation to avoid heavy imports + with patch("lfx.base.data.docling_utils.DocumentConverter") as mock_converter: + mock_instance1 = MagicMock() + mock_instance2 = MagicMock() + mock_converter.side_effect = [mock_instance1, mock_instance2] + + # First call with specific parameters + result1 = _get_cached_converter( + pipeline="standard", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Second call with same parameters should return cached result + result2 = _get_cached_converter( + pipeline="standard", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Third call with different parameters should create new instance + result3 = _get_cached_converter( + pipeline="vlm", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Verify caching behavior + assert result1 is result2, "Same parameters should return cached instance" + assert result1 is not result3, "Different parameters should return new instance" + + # Verify DocumentConverter was only called twice (not three times) + assert mock_converter.call_count == 2 + + # Verify cache statistics + cache_info = _get_cached_converter.cache_info() + assert cache_info.hits >= 1, "Should have at least one cache hit" + assert cache_info.misses == 2, "Should have exactly two cache misses" + + def test_cached_converter_lru_eviction(self): + """Test that LRU cache properly evicts old entries when maxsize is reached.""" + from lfx.base.data.docling_utils import _get_cached_converter + + # Clear cache before test + _get_cached_converter.cache_clear() + + with patch("lfx.base.data.docling_utils.DocumentConverter") as mock_converter: + mock_instances = [MagicMock() for _ in range(5)] + mock_converter.side_effect = mock_instances + + # Create 5 different cache entries (maxsize=4, so one should be evicted) + configs = [ + ("standard", "None", False, None), + ("standard", "easyocr", False, None), + ("vlm", "None", False, None), + ("standard", "None", True, None), + ("vlm", "easyocr", False, None), + ] + + for pipeline, ocr, pic_class, pic_hash in configs: + _get_cached_converter( + pipeline=pipeline, + ocr_engine=ocr, + do_picture_classification=pic_class, + pic_desc_config_hash=pic_hash, + ) + + # Cache size should be at most 4 (maxsize) + cache_info = _get_cached_converter.cache_info() + assert cache_info.currsize <= 4, "Cache size should not exceed maxsize" + + def test_cached_converter_performance_improvement(self): + """Test that caching provides performance improvement.""" + from lfx.base.data.docling_utils import _get_cached_converter + + # Clear cache before test + _get_cached_converter.cache_clear() + + with patch("lfx.base.data.docling_utils.DocumentConverter") as mock_converter: + # Simulate slow converter creation + def slow_creation(*args, **kwargs): # noqa: ARG001 + time.sleep(0.05) # 50ms delay + return MagicMock() + + mock_converter.side_effect = slow_creation + + # First call (cache miss - should be slow) + start_time = time.time() + _get_cached_converter( + pipeline="standard", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + first_call_duration = time.time() - start_time + + # Second call (cache hit - should be fast) + start_time = time.time() + _get_cached_converter( + pipeline="standard", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + second_call_duration = time.time() - start_time + + # Cache hit should be significantly faster (at least 10x) + assert second_call_duration < first_call_duration / 10, ( + f"Cache hit should be much faster: " + f"first={first_call_duration:.4f}s, second={second_call_duration:.4f}s" + ) + + def test_cache_clear(self): + """Test that cache can be cleared.""" + from lfx.base.data.docling_utils import _get_cached_converter + + # Clear cache + _get_cached_converter.cache_clear() + + with patch("lfx.base.data.docling_utils.DocumentConverter"): + # Add something to cache + _get_cached_converter( + pipeline="standard", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Verify cache has content + cache_info = _get_cached_converter.cache_info() + assert cache_info.currsize > 0 + + # Clear cache + _get_cached_converter.cache_clear() + + # Verify cache is empty + cache_info = _get_cached_converter.cache_info() + assert cache_info.currsize == 0 + assert cache_info.hits == 0 + assert cache_info.misses == 0 + + def test_different_ocr_engines_create_different_caches(self): + """Test that different OCR engines result in different cached converters.""" + from lfx.base.data.docling_utils import _get_cached_converter + + _get_cached_converter.cache_clear() + + with patch("lfx.base.data.docling_utils.DocumentConverter") as mock_converter: + mock_instance1 = MagicMock() + mock_instance2 = MagicMock() + mock_converter.side_effect = [mock_instance1, mock_instance2] + + # Create converter with no OCR + result1 = _get_cached_converter( + pipeline="standard", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Create converter with EasyOCR + result2 = _get_cached_converter( + pipeline="standard", + ocr_engine="easyocr", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Should be different instances + assert result1 is not result2 + assert mock_converter.call_count == 2 + + def test_different_pipelines_create_different_caches(self): + """Test that different pipelines result in different cached converters.""" + from lfx.base.data.docling_utils import _get_cached_converter + + _get_cached_converter.cache_clear() + + with patch("lfx.base.data.docling_utils.DocumentConverter") as mock_converter: + mock_instance1 = MagicMock() + mock_instance2 = MagicMock() + mock_converter.side_effect = [mock_instance1, mock_instance2] + + # Create converter with standard pipeline + result1 = _get_cached_converter( + pipeline="standard", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Create converter with VLM pipeline + result2 = _get_cached_converter( + pipeline="vlm", + ocr_engine="None", + do_picture_classification=False, + pic_desc_config_hash=None, + ) + + # Should be different instances + assert result1 is not result2 + assert mock_converter.call_count == 2 From 5e8b8204a77ce2327ca0e2bb8e16ce37d172a216 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:41:59 +0000 Subject: [PATCH 2/8] [autofix.ci] apply automated fixes --- src/lfx/src/lfx/base/data/docling_utils.py | 1 + src/lfx/tests/unit/base/data/test_docling_utils.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lfx/src/lfx/base/data/docling_utils.py b/src/lfx/src/lfx/base/data/docling_utils.py index 2a8ff66d2c7e..aa0e4267bebc 100644 --- a/src/lfx/src/lfx/base/data/docling_utils.py +++ b/src/lfx/src/lfx/base/data/docling_utils.py @@ -223,6 +223,7 @@ def _get_vlm_opts() -> VlmPipelineOptions: return DocumentConverter(format_options=format_options) + def docling_worker( *, file_paths: list[str], diff --git a/src/lfx/tests/unit/base/data/test_docling_utils.py b/src/lfx/tests/unit/base/data/test_docling_utils.py index 4c07fe87f9fa..5058bf7419eb 100644 --- a/src/lfx/tests/unit/base/data/test_docling_utils.py +++ b/src/lfx/tests/unit/base/data/test_docling_utils.py @@ -271,8 +271,7 @@ def slow_creation(*args, **kwargs): # noqa: ARG001 # Cache hit should be significantly faster (at least 10x) assert second_call_duration < first_call_duration / 10, ( - f"Cache hit should be much faster: " - f"first={first_call_duration:.4f}s, second={second_call_duration:.4f}s" + f"Cache hit should be much faster: first={first_call_duration:.4f}s, second={second_call_duration:.4f}s" ) def test_cache_clear(self): From c754b1eba618ef7c77601847f36e0c99cad1b2b2 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:44:05 +0000 Subject: [PATCH 3/8] [autofix.ci] apply automated fixes (attempt 2/3) --- .../Basic Prompt Chaining.json | 10 +- .../starter_projects/Basic Prompting.json | 6 +- .../starter_projects/Blog Writer.json | 6 +- .../Custom Component Generator.json | 6 +- .../starter_projects/Document Q&A.json | 6 +- .../Financial Report Parser.json | 59 +- .../starter_projects/Hybrid Search RAG.json | 61 +- .../Image Sentiment Analysis.json | 61 +- .../Instagram Copywriter.json | 361 +----- .../starter_projects/Invoice Summarizer.json | 357 +----- .../starter_projects/Knowledge Retrieval.json | 4 +- .../starter_projects/Market Research.json | 412 ++---- .../starter_projects/Meeting Summary.json | 16 +- .../starter_projects/Memory Chatbot.json | 6 +- .../starter_projects/News Aggregator.json | 365 +----- .../starter_projects/Nvidia Remix.json | 487 +------- .../Pok\303\251dex Agent.json" | 361 +----- .../Portfolio Website Code Generator.json | 61 +- .../starter_projects/Price Deal Finder.json | 357 +----- .../starter_projects/Research Agent.json | 361 +----- .../Research Translation Loop.json | 6 +- .../SEO Keyword Generator.json | 6 +- .../starter_projects/SaaS Pricing.json | 357 +----- .../starter_projects/Search agent.json | 357 +----- .../Sequential Tasks Agents.json | 1099 ++--------------- .../starter_projects/Simple Agent.json | 357 +----- .../starter_projects/Social Media Agent.json | 357 +----- .../Text Sentiment Analysis.json | 14 +- .../Travel Planning Agents.json | 1067 ++-------------- .../Twitter Thread Generator.json | 6 +- .../starter_projects/Vector Store RAG.json | 6 +- .../starter_projects/Youtube Analysis.json | 385 +----- 32 files changed, 1008 insertions(+), 6372 deletions(-) diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json index 03a6af0af64c..546321388707 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json @@ -628,7 +628,7 @@ "legacy": false, "lf_version": "1.5.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -702,7 +702,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1258,7 +1258,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -1580,7 +1580,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -1901,7 +1901,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json index a999855e96a2..caa172265f26 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json @@ -580,7 +580,7 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -654,7 +654,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -955,7 +955,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json index 255a654b1a25..99160a5337a7 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json @@ -473,7 +473,7 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -547,7 +547,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1454,7 +1454,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageTextInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json index d1a73ec2f769..820b8d0b3846 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Generator.json @@ -2227,7 +2227,7 @@ "key": "ChatOutput", "legacy": false, "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -2303,7 +2303,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -2615,7 +2615,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json index 4f9b968d541e..cf32ba3bc9f0 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json @@ -409,7 +409,7 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -483,7 +483,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -979,7 +979,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json index d4005bce5430..4d991a6ea4fb 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json @@ -150,7 +150,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -226,7 +226,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -871,7 +871,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageTextInput", @@ -1089,7 +1089,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "ce82f9d7948c", + "code_hash": "058ca1f51e9f", "dependencies": { "dependencies": [ { @@ -1144,6 +1144,26 @@ "pinned": false, "template": { "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": true, + "display_name": "API Key", + "dynamic": false, + "info": "Model Provider API key", + "input_types": [], + "load_from_db": true, + "name": "api_key", + "override_skip": false, + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "track_in_telemetry": false, + "type": "str", + "value": "" + }, "code": { "advanced": true, "dynamic": true, @@ -1160,7 +1180,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n HandleInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(self.llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = self.llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" + "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n MessageTextInput,\n ModelInput,\n MultilineInput,\n Output,\n SecretStrInput,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)\n\n if not hasattr(llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(llm, output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(llm, output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" }, "input_value": { "_input_type": "MultilineInput", @@ -1187,26 +1207,41 @@ "type": "str", "value": "" }, - "llm": { - "_input_type": "HandleInput", + "model": { + "_input_type": "ModelInput", "advanced": false, "display_name": "Language Model", "dynamic": false, - "info": "The language model to use to generate the structured output.", + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } + }, + "info": "Select your model provider", "input_types": [ "LanguageModel" ], "list": false, "list_add_label": "Add More", - "name": "llm", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, "required": true, "show": true, "title_case": false, - "trace_as_metadata": true, + "tool_mode": false, + "trace_as_input": true, "track_in_telemetry": false, - "type": "other", + "type": "model", "value": "" }, "output_schema": { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json index 8824e74db18b..68d34480f8bd 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json @@ -725,7 +725,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -800,7 +800,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1296,7 +1296,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -1623,7 +1623,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -2695,7 +2695,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "ce82f9d7948c", + "code_hash": "058ca1f51e9f", "dependencies": { "dependencies": [ { @@ -2750,6 +2750,26 @@ "pinned": false, "template": { "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": true, + "display_name": "API Key", + "dynamic": false, + "info": "Model Provider API key", + "input_types": [], + "load_from_db": true, + "name": "api_key", + "override_skip": false, + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "track_in_telemetry": false, + "type": "str", + "value": "" + }, "code": { "advanced": true, "dynamic": true, @@ -2766,7 +2786,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n HandleInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(self.llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = self.llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" + "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n MessageTextInput,\n ModelInput,\n MultilineInput,\n Output,\n SecretStrInput,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)\n\n if not hasattr(llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(llm, output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(llm, output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" }, "input_value": { "_input_type": "MultilineInput", @@ -2793,26 +2813,41 @@ "type": "str", "value": "" }, - "llm": { - "_input_type": "HandleInput", + "model": { + "_input_type": "ModelInput", "advanced": false, "display_name": "Language Model", "dynamic": false, - "info": "The language model to use to generate the structured output.", + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } + }, + "info": "Select your model provider", "input_types": [ "LanguageModel" ], "list": false, "list_add_label": "Add More", - "name": "llm", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, "required": true, "show": true, "title_case": false, - "trace_as_metadata": true, + "tool_mode": false, + "trace_as_input": true, "track_in_telemetry": false, - "type": "other", + "type": "model", "value": "" }, "output_schema": { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json index 626bf15fa9c2..6884704e7e68 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json @@ -506,7 +506,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -580,7 +580,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1226,7 +1226,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageTextInput", @@ -1551,7 +1551,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageTextInput", @@ -1769,7 +1769,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "ce82f9d7948c", + "code_hash": "058ca1f51e9f", "dependencies": { "dependencies": [ { @@ -1824,6 +1824,26 @@ "pinned": false, "template": { "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": true, + "display_name": "API Key", + "dynamic": false, + "info": "Model Provider API key", + "input_types": [], + "load_from_db": true, + "name": "api_key", + "override_skip": false, + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "track_in_telemetry": false, + "type": "str", + "value": "" + }, "code": { "advanced": true, "dynamic": true, @@ -1840,7 +1860,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n HandleInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(self.llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = self.llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" + "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n MessageTextInput,\n ModelInput,\n MultilineInput,\n Output,\n SecretStrInput,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)\n\n if not hasattr(llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(llm, output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(llm, output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" }, "input_value": { "_input_type": "MultilineInput", @@ -1867,26 +1887,41 @@ "type": "str", "value": "" }, - "llm": { - "_input_type": "HandleInput", + "model": { + "_input_type": "ModelInput", "advanced": false, "display_name": "Language Model", "dynamic": false, - "info": "The language model to use to generate the structured output.", + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } + }, + "info": "Select your model provider", "input_types": [ "LanguageModel" ], "list": false, "list_add_label": "Add More", - "name": "llm", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, "required": true, "show": true, "title_case": false, - "trace_as_metadata": true, + "tool_mode": false, + "trace_as_input": true, "track_in_telemetry": false, - "type": "other", + "type": "model", "value": "" }, "output_schema": { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json index 72ee5461d72a..84acce08955d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json @@ -1034,7 +1034,7 @@ "icon": "MessagesSquare", "legacy": false, "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -1108,7 +1108,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1951,13 +1951,9 @@ "last_updated": "2025-07-18T17:42:31.005Z", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1965,6 +1961,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -2054,71 +2054,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -2131,27 +2072,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -2168,7 +2088,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -2277,137 +2197,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -2427,27 +2252,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -2510,47 +2314,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -2576,56 +2339,6 @@ "type": "str", "value": "You are a helpful AI assistant. Use the following information from a web search to answer the user's question. If the search results don't contain relevant information, say so and offer to help with something else." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -2813,7 +2526,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -3135,7 +2848,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json b/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json index 9e41813b99ed..5a891a358bf5 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json @@ -305,7 +305,7 @@ "legacy": false, "lf_version": "1.1.5", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -380,7 +380,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1151,13 +1151,9 @@ "key": "Agent", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1165,6 +1161,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1238,71 +1238,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1315,27 +1256,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1352,7 +1272,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1461,137 +1381,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1611,27 +1436,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1694,47 +1498,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1760,56 +1523,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Knowledge Retrieval.json b/src/backend/base/langflow/initial_setup/starter_projects/Knowledge Retrieval.json index 584a4165dcb0..3fb7125b4cca 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Knowledge Retrieval.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Knowledge Retrieval.json @@ -232,7 +232,7 @@ "legacy": false, "lf_version": "1.5.0.post1", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -307,7 +307,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json index 8ec2bc6625e5..600ec6cca350 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json @@ -474,7 +474,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -548,7 +548,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1306,7 +1306,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -1543,13 +1543,9 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1557,6 +1553,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1630,71 +1630,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1707,27 +1648,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1744,7 +1664,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1853,137 +1773,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -2003,27 +1828,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -2086,47 +1890,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -2152,56 +1915,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -2471,7 +2184,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "ce82f9d7948c", + "code_hash": "058ca1f51e9f", "dependencies": { "dependencies": [ { @@ -2526,6 +2239,26 @@ "pinned": false, "template": { "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": true, + "display_name": "API Key", + "dynamic": false, + "info": "Model Provider API key", + "input_types": [], + "load_from_db": true, + "name": "api_key", + "override_skip": false, + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "track_in_telemetry": false, + "type": "str", + "value": "" + }, "code": { "advanced": true, "dynamic": true, @@ -2542,7 +2275,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n HandleInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(self.llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = self.llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" + "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n MessageTextInput,\n ModelInput,\n MultilineInput,\n Output,\n SecretStrInput,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)\n\n if not hasattr(llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(llm, output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(llm, output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" }, "input_value": { "_input_type": "MultilineInput", @@ -2569,26 +2302,41 @@ "type": "str", "value": "" }, - "llm": { - "_input_type": "HandleInput", + "model": { + "_input_type": "ModelInput", "advanced": false, "display_name": "Language Model", "dynamic": false, - "info": "The language model to use to generate the structured output.", + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } + }, + "info": "Select your model provider", "input_types": [ "LanguageModel" ], "list": false, "list_add_label": "Add More", - "name": "llm", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, "required": true, "show": true, "title_case": false, - "trace_as_metadata": true, + "tool_mode": false, + "trace_as_input": true, "track_in_telemetry": false, - "type": "other", + "type": "model", "value": "" }, "output_schema": { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json index 0e7cb6a33398..18c08a2e43f7 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json @@ -639,7 +639,7 @@ "legacy": false, "lf_version": "1.1.5", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -715,7 +715,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -918,7 +918,7 @@ "legacy": false, "lf_version": "1.1.1", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -994,7 +994,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1197,7 +1197,7 @@ "legacy": false, "lf_version": "1.1.5", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -1273,7 +1273,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -3073,7 +3073,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -3400,7 +3400,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json index afb82fc22cb1..f43e4d1f5f91 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json @@ -421,7 +421,7 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -495,7 +495,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1358,7 +1358,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json index cd6d24dd0450..ed1f4f59ce89 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json @@ -880,7 +880,7 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -955,7 +955,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1175,13 +1175,9 @@ "last_updated": "2025-09-30T16:16:12.101Z", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1189,6 +1185,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1262,71 +1262,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1339,27 +1280,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1376,7 +1296,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1485,137 +1405,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1635,27 +1460,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1718,47 +1522,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1784,56 +1547,6 @@ "type": "str", "value": "You are a helpful content writer researching news and social posts for our company.\n\nCreate a new JSON file and insert the extracted data into that file.\n\nYou will use the AgentQL tool when getting content from URLs. Be sure to get the URL, author, content, and publish date. Here's how to write an AgentQL query:\n\nThe AgentQL query serves as the building block of your script. This guide shows you how AgentQL's query structure works and how to write a valid query.\n\n### Single term query\n\nA **single term query** enables you to retrieve a single element on the webpage. Here is an example of how you can write a single term query to retrieve a search box.\n\n```AgentQL\n{\n search_box\n}\n```\n\n### List term query\n\nA **list term query** enables you to retrieve a list of similar elements on the webpage. Here is an example of how you can write a list term query to retrieve a list of prices of apples.\n\n```AgentQL\n{\n apple_price[]\n}\n```\n\nYou can also specify the exact field you want to return in the list. Here is an example of how you can specify that you want the name and price from the list of products.\n\n```AgentQL\n{\n products[] {\n name\n price(integer)\n }\n}\n```\n\n### Combining single term queries and list term queries\n\nYou can query for both **single terms** and **list terms** by combining the preceding formats.\n\n```AgentQL\n{\n author\n date_of_birth\n book_titles[]\n}\n```\n\n### Giving context to queries\n\nThere two main ways you can provide additional context to your queries.\n\n#### Structural context\n\nYou can nest queries within parent containers to indicate that your target web element is in a particular section of the webpage.\n\n```AgentQL\n{\n footer {\n social_media_links[]\n }\n}\n```\n\n#### Semantic context\n\nYou can also provide a short description within parentheses to guide AgentQL in locating the right element(s).\n\n```AgentQL\n{\n footer {\n social_media_links(The icons that lead to Facebook, Snapchat, etc.)[]\n }\n}\n```\n\n### Syntax guidelines\n\nEnclose all AgentQL query terms within curly braces `{}`. The following query structure isn't valid because the term \"social_media_links\" is wrongly enclosed within parenthesis`()`.\n\n```AgentQL\n( # Should be {\n social_media_links(The icons that lead to Facebook, Snapchat, etc.)[]\n) # Should be }\n```\n\nYou can't include new lines in your semantic context. The following query structure isn't valid because the semantic context isn't contained within one line.\n\n```AgentQL\n{\n social_media_links(The icons that lead\n to Facebook, Snapchat, etc.)[]\n}\n```" }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -1925,7 +1638,7 @@ "last_updated": "2025-09-30T16:16:26.172Z", "legacy": false, "metadata": { - "code_hash": "16f338bf1a3f", + "code_hash": "9fb8e92f7b34", "dependencies": { "dependencies": [ { @@ -1991,7 +1704,7 @@ "advanced": false, "display_name": "Append", "dynamic": false, - "info": "Append to file if it exists (only for plain text formats). Disabled for binary formats like Excel.", + "info": "Append to file if it exists (only for Local storage with plain text formats). Not supported for cloud storage (AWS/Google Drive).", "list": false, "list_add_label": "Add More", "name": "append_mode", @@ -2130,7 +1843,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nfrom collections.abc import AsyncIterator, Iterator\nfrom pathlib import Path\n\nimport orjson\nimport pandas as pd\nfrom fastapi import UploadFile\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.custom import Component\nfrom lfx.inputs import SortableListInput\nfrom lfx.io import BoolInput, DropdownInput, HandleInput, SecretStrInput, StrInput\nfrom lfx.schema import Data, DataFrame, Message\nfrom lfx.services.deps import get_settings_service, get_storage_service, session_scope\nfrom lfx.template.field.base import Output\n\n\nclass SaveToFileComponent(Component):\n display_name = \"Write File\"\n description = \"Save data to local file, AWS S3, or Google Drive in the selected format.\"\n documentation: str = \"https://docs.langflow.org/write-file\"\n icon = \"file-text\"\n name = \"SaveToFile\"\n\n # File format options for different storage types\n LOCAL_DATA_FORMAT_CHOICES = [\"csv\", \"excel\", \"json\", \"markdown\"]\n LOCAL_MESSAGE_FORMAT_CHOICES = [\"txt\", \"json\", \"markdown\"]\n AWS_FORMAT_CHOICES = [\n \"txt\",\n \"json\",\n \"csv\",\n \"xml\",\n \"html\",\n \"md\",\n \"yaml\",\n \"log\",\n \"tsv\",\n \"jsonl\",\n \"parquet\",\n \"xlsx\",\n \"zip\",\n ]\n GDRIVE_FORMAT_CHOICES = [\"txt\", \"json\", \"csv\", \"xlsx\", \"slides\", \"docs\", \"jpg\", \"mp3\"]\n\n inputs = [\n # Storage location selection\n SortableListInput(\n name=\"storage_location\",\n display_name=\"Storage Location\",\n placeholder=\"Select Location\",\n info=\"Choose where to save the file.\",\n options=[\n {\"name\": \"Local\", \"icon\": \"hard-drive\"},\n {\"name\": \"AWS\", \"icon\": \"Amazon\"},\n {\"name\": \"Google Drive\", \"icon\": \"google\"},\n ],\n real_time_refresh=True,\n limit=1,\n ),\n # Common inputs\n HandleInput(\n name=\"input\",\n display_name=\"File Content\",\n info=\"The input to save.\",\n dynamic=True,\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n StrInput(\n name=\"file_name\",\n display_name=\"File Name\",\n info=\"Name file will be saved as (without extension).\",\n required=True,\n show=False,\n tool_mode=True,\n ),\n BoolInput(\n name=\"append_mode\",\n display_name=\"Append\",\n info=\"Append to file if it exists (only for plain text formats). Disabled for binary formats like Excel.\",\n value=False,\n show=False,\n ),\n # Format inputs (dynamic based on storage location)\n DropdownInput(\n name=\"local_format\",\n display_name=\"File Format\",\n options=list(dict.fromkeys(LOCAL_DATA_FORMAT_CHOICES + LOCAL_MESSAGE_FORMAT_CHOICES)),\n info=\"Select the file format for local storage.\",\n value=\"json\",\n show=False,\n ),\n DropdownInput(\n name=\"aws_format\",\n display_name=\"File Format\",\n options=AWS_FORMAT_CHOICES,\n info=\"Select the file format for AWS S3 storage.\",\n value=\"txt\",\n show=False,\n ),\n DropdownInput(\n name=\"gdrive_format\",\n display_name=\"File Format\",\n options=GDRIVE_FORMAT_CHOICES,\n info=\"Select the file format for Google Drive storage.\",\n value=\"txt\",\n show=False,\n ),\n # AWS S3 specific inputs\n SecretStrInput(\n name=\"aws_access_key_id\",\n display_name=\"AWS Access Key ID\",\n info=\"AWS Access key ID.\",\n show=False,\n advanced=True,\n ),\n SecretStrInput(\n name=\"aws_secret_access_key\",\n display_name=\"AWS Secret Key\",\n info=\"AWS Secret Key.\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"bucket_name\",\n display_name=\"S3 Bucket Name\",\n info=\"Enter the name of the S3 bucket.\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"aws_region\",\n display_name=\"AWS Region\",\n info=\"AWS region (e.g., us-east-1, eu-west-1).\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"s3_prefix\",\n display_name=\"S3 Prefix\",\n info=\"Prefix for all files in S3.\",\n show=False,\n advanced=True,\n ),\n # Google Drive specific inputs\n SecretStrInput(\n name=\"service_account_key\",\n display_name=\"GCP Credentials Secret Key\",\n info=\"Your Google Cloud Platform service account JSON key as a secret string (complete JSON content).\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"folder_id\",\n display_name=\"Google Drive Folder ID\",\n info=(\n \"The Google Drive folder ID where the file will be uploaded. \"\n \"The folder must be shared with the service account email.\"\n ),\n show=False,\n advanced=True,\n ),\n ]\n\n outputs = [Output(display_name=\"File Path\", name=\"message\", method=\"save_to_file\")]\n\n def update_build_config(self, build_config, field_value, field_name=None):\n \"\"\"Update build configuration to show/hide fields based on storage location selection.\"\"\"\n if field_name != \"storage_location\":\n return build_config\n\n # Extract selected storage location\n selected = [location[\"name\"] for location in field_value] if isinstance(field_value, list) else []\n\n # Hide all dynamic fields first\n dynamic_fields = [\n \"file_name\", # Common fields (input is always visible)\n \"append_mode\",\n \"local_format\",\n \"aws_format\",\n \"gdrive_format\",\n \"aws_access_key_id\",\n \"aws_secret_access_key\",\n \"bucket_name\",\n \"aws_region\",\n \"s3_prefix\",\n \"service_account_key\",\n \"folder_id\",\n ]\n\n for f_name in dynamic_fields:\n if f_name in build_config:\n build_config[f_name][\"show\"] = False\n\n # Show fields based on selected storage location\n if len(selected) == 1:\n location = selected[0]\n\n # Show file_name and append_mode when any storage location is selected\n if \"file_name\" in build_config:\n build_config[\"file_name\"][\"show\"] = True\n if \"append_mode\" in build_config:\n build_config[\"append_mode\"][\"show\"] = True\n\n if location == \"Local\":\n if \"local_format\" in build_config:\n build_config[\"local_format\"][\"show\"] = True\n\n elif location == \"AWS\":\n aws_fields = [\n \"aws_format\",\n \"aws_access_key_id\",\n \"aws_secret_access_key\",\n \"bucket_name\",\n \"aws_region\",\n \"s3_prefix\",\n ]\n for f_name in aws_fields:\n if f_name in build_config:\n build_config[f_name][\"show\"] = True\n\n elif location == \"Google Drive\":\n gdrive_fields = [\"gdrive_format\", \"service_account_key\", \"folder_id\"]\n for f_name in gdrive_fields:\n if f_name in build_config:\n build_config[f_name][\"show\"] = True\n\n return build_config\n\n async def save_to_file(self) -> Message:\n \"\"\"Save the input to a file and upload it, returning a confirmation message.\"\"\"\n # Validate inputs\n if not self.file_name:\n msg = \"File name must be provided.\"\n raise ValueError(msg)\n if not self._get_input_type():\n msg = \"Input type is not set.\"\n raise ValueError(msg)\n\n # Get selected storage location\n storage_location = self._get_selected_storage_location()\n if not storage_location:\n msg = \"Storage location must be selected.\"\n raise ValueError(msg)\n\n # Route to appropriate save method based on storage location\n if storage_location == \"Local\":\n return await self._save_to_local()\n if storage_location == \"AWS\":\n return await self._save_to_aws()\n if storage_location == \"Google Drive\":\n return await self._save_to_google_drive()\n msg = f\"Unsupported storage location: {storage_location}\"\n raise ValueError(msg)\n\n def _get_input_type(self) -> str:\n \"\"\"Determine the input type based on the provided input.\"\"\"\n # Use exact type checking (type() is) instead of isinstance() to avoid inheritance issues.\n # Since Message inherits from Data, isinstance(message, Data) would return True for Message objects,\n # causing Message inputs to be incorrectly identified as Data type.\n if type(self.input) is DataFrame:\n return \"DataFrame\"\n if type(self.input) is Message:\n return \"Message\"\n if type(self.input) is Data:\n return \"Data\"\n msg = f\"Unsupported input type: {type(self.input)}\"\n raise ValueError(msg)\n\n def _get_default_format(self) -> str:\n \"\"\"Return the default file format based on input type.\"\"\"\n if self._get_input_type() == \"DataFrame\":\n return \"csv\"\n if self._get_input_type() == \"Data\":\n return \"json\"\n if self._get_input_type() == \"Message\":\n return \"json\"\n return \"json\" # Fallback\n\n def _adjust_file_path_with_format(self, path: Path, fmt: str) -> Path:\n \"\"\"Adjust the file path to include the correct extension.\"\"\"\n file_extension = path.suffix.lower().lstrip(\".\")\n if fmt == \"excel\":\n return Path(f\"{path}.xlsx\").expanduser() if file_extension not in [\"xlsx\", \"xls\"] else path\n return Path(f\"{path}.{fmt}\").expanduser() if file_extension != fmt else path\n\n def _is_plain_text_format(self, fmt: str) -> bool:\n \"\"\"Check if a file format is plain text (supports appending).\"\"\"\n plain_text_formats = [\"txt\", \"json\", \"markdown\", \"md\", \"csv\", \"xml\", \"html\", \"yaml\", \"log\", \"tsv\", \"jsonl\"]\n return fmt.lower() in plain_text_formats\n\n async def _upload_file(self, file_path: Path) -> None:\n \"\"\"Upload the saved file using the upload_user_file service.\"\"\"\n from langflow.api.v2.files import upload_user_file\n from langflow.services.database.models.user.crud import get_user_by_id\n\n # Ensure the file exists\n if not file_path.exists():\n msg = f\"File not found: {file_path}\"\n raise FileNotFoundError(msg)\n\n # Upload the file - always use append=False because the local file already contains\n # the correct content (either new or appended locally)\n with file_path.open(\"rb\") as f:\n async with session_scope() as db:\n if not self.user_id:\n msg = \"User ID is required for file saving.\"\n raise ValueError(msg)\n current_user = await get_user_by_id(db, self.user_id)\n\n await upload_user_file(\n file=UploadFile(filename=file_path.name, file=f, size=file_path.stat().st_size),\n session=db,\n current_user=current_user,\n storage_service=get_storage_service(),\n settings_service=get_settings_service(),\n append=False,\n )\n\n def _save_dataframe(self, dataframe: DataFrame, path: Path, fmt: str) -> str:\n \"\"\"Save a DataFrame to the specified file format.\"\"\"\n append_mode = getattr(self, \"append_mode\", False)\n should_append = append_mode and path.exists() and self._is_plain_text_format(fmt)\n\n if fmt == \"csv\":\n dataframe.to_csv(path, index=False, mode=\"a\" if should_append else \"w\", header=not should_append)\n elif fmt == \"excel\":\n dataframe.to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n if should_append:\n # Read and parse existing JSON\n existing_data = []\n try:\n existing_content = path.read_text(encoding=\"utf-8\").strip()\n if existing_content:\n parsed = json.loads(existing_content)\n # Handle case where existing content is a single object\n if isinstance(parsed, dict):\n existing_data = [parsed]\n elif isinstance(parsed, list):\n existing_data = parsed\n except (json.JSONDecodeError, FileNotFoundError):\n # Treat parse errors or missing file as empty array\n existing_data = []\n\n # Append new data\n new_records = json.loads(dataframe.to_json(orient=\"records\"))\n existing_data.extend(new_records)\n\n # Write back as a single JSON array\n path.write_text(json.dumps(existing_data, indent=2), encoding=\"utf-8\")\n else:\n dataframe.to_json(path, orient=\"records\", indent=2)\n elif fmt == \"markdown\":\n content = dataframe.to_markdown(index=False)\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\\n\" + content, encoding=\"utf-8\")\n else:\n path.write_text(content, encoding=\"utf-8\")\n else:\n msg = f\"Unsupported DataFrame format: {fmt}\"\n raise ValueError(msg)\n action = \"appended to\" if should_append else \"saved successfully as\"\n return f\"DataFrame {action} '{path}'\"\n\n def _save_data(self, data: Data, path: Path, fmt: str) -> str:\n \"\"\"Save a Data object to the specified file format.\"\"\"\n append_mode = getattr(self, \"append_mode\", False)\n should_append = append_mode and path.exists() and self._is_plain_text_format(fmt)\n\n if fmt == \"csv\":\n pd.DataFrame(data.data).to_csv(\n path,\n index=False,\n mode=\"a\" if should_append else \"w\",\n header=not should_append,\n )\n elif fmt == \"excel\":\n pd.DataFrame(data.data).to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n new_data = jsonable_encoder(data.data)\n if should_append:\n # Read and parse existing JSON\n existing_data = []\n try:\n existing_content = path.read_text(encoding=\"utf-8\").strip()\n if existing_content:\n parsed = json.loads(existing_content)\n # Handle case where existing content is a single object\n if isinstance(parsed, dict):\n existing_data = [parsed]\n elif isinstance(parsed, list):\n existing_data = parsed\n except (json.JSONDecodeError, FileNotFoundError):\n # Treat parse errors or missing file as empty array\n existing_data = []\n\n # Append new data\n if isinstance(new_data, list):\n existing_data.extend(new_data)\n else:\n existing_data.append(new_data)\n\n # Write back as a single JSON array\n path.write_text(json.dumps(existing_data, indent=2), encoding=\"utf-8\")\n else:\n content = orjson.dumps(new_data, option=orjson.OPT_INDENT_2).decode(\"utf-8\")\n path.write_text(content, encoding=\"utf-8\")\n elif fmt == \"markdown\":\n content = pd.DataFrame(data.data).to_markdown(index=False)\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\\n\" + content, encoding=\"utf-8\")\n else:\n path.write_text(content, encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Data format: {fmt}\"\n raise ValueError(msg)\n action = \"appended to\" if should_append else \"saved successfully as\"\n return f\"Data {action} '{path}'\"\n\n async def _save_message(self, message: Message, path: Path, fmt: str) -> str:\n \"\"\"Save a Message to the specified file format, handling async iterators.\"\"\"\n content = \"\"\n if message.text is None:\n content = \"\"\n elif isinstance(message.text, AsyncIterator):\n async for item in message.text:\n content += str(item) + \" \"\n content = content.strip()\n elif isinstance(message.text, Iterator):\n content = \" \".join(str(item) for item in message.text)\n else:\n content = str(message.text)\n\n append_mode = getattr(self, \"append_mode\", False)\n should_append = append_mode and path.exists() and self._is_plain_text_format(fmt)\n\n if fmt == \"txt\":\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\" + content, encoding=\"utf-8\")\n else:\n path.write_text(content, encoding=\"utf-8\")\n elif fmt == \"json\":\n new_message = {\"message\": content}\n if should_append:\n # Read and parse existing JSON\n existing_data = []\n try:\n existing_content = path.read_text(encoding=\"utf-8\").strip()\n if existing_content:\n parsed = json.loads(existing_content)\n # Handle case where existing content is a single object\n if isinstance(parsed, dict):\n existing_data = [parsed]\n elif isinstance(parsed, list):\n existing_data = parsed\n except (json.JSONDecodeError, FileNotFoundError):\n # Treat parse errors or missing file as empty array\n existing_data = []\n\n # Append new message\n existing_data.append(new_message)\n\n # Write back as a single JSON array\n path.write_text(json.dumps(existing_data, indent=2), encoding=\"utf-8\")\n else:\n path.write_text(json.dumps(new_message, indent=2), encoding=\"utf-8\")\n elif fmt == \"markdown\":\n md_content = f\"**Message:**\\n\\n{content}\"\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\\n\" + md_content, encoding=\"utf-8\")\n else:\n path.write_text(md_content, encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Message format: {fmt}\"\n raise ValueError(msg)\n action = \"appended to\" if should_append else \"saved successfully as\"\n return f\"Message {action} '{path}'\"\n\n def _get_selected_storage_location(self) -> str:\n \"\"\"Get the selected storage location from the SortableListInput.\"\"\"\n if hasattr(self, \"storage_location\") and self.storage_location:\n if isinstance(self.storage_location, list) and len(self.storage_location) > 0:\n return self.storage_location[0].get(\"name\", \"\")\n if isinstance(self.storage_location, dict):\n return self.storage_location.get(\"name\", \"\")\n return \"\"\n\n def _get_file_format_for_location(self, location: str) -> str:\n \"\"\"Get the appropriate file format based on storage location.\"\"\"\n if location == \"Local\":\n return getattr(self, \"local_format\", None) or self._get_default_format()\n if location == \"AWS\":\n return getattr(self, \"aws_format\", \"txt\")\n if location == \"Google Drive\":\n return getattr(self, \"gdrive_format\", \"txt\")\n return self._get_default_format()\n\n async def _save_to_local(self) -> Message:\n \"\"\"Save file to local storage (original functionality).\"\"\"\n file_format = self._get_file_format_for_location(\"Local\")\n\n # Validate file format based on input type\n allowed_formats = (\n self.LOCAL_MESSAGE_FORMAT_CHOICES if self._get_input_type() == \"Message\" else self.LOCAL_DATA_FORMAT_CHOICES\n )\n if file_format not in allowed_formats:\n msg = f\"Invalid file format '{file_format}' for {self._get_input_type()}. Allowed: {allowed_formats}\"\n raise ValueError(msg)\n\n # Prepare file path\n file_path = Path(self.file_name).expanduser()\n if not file_path.parent.exists():\n file_path.parent.mkdir(parents=True, exist_ok=True)\n file_path = self._adjust_file_path_with_format(file_path, file_format)\n\n # Save the input to file based on type\n if self._get_input_type() == \"DataFrame\":\n confirmation = self._save_dataframe(self.input, file_path, file_format)\n elif self._get_input_type() == \"Data\":\n confirmation = self._save_data(self.input, file_path, file_format)\n elif self._get_input_type() == \"Message\":\n confirmation = await self._save_message(self.input, file_path, file_format)\n else:\n msg = f\"Unsupported input type: {self._get_input_type()}\"\n raise ValueError(msg)\n\n # Upload the saved file\n await self._upload_file(file_path)\n\n # Return the final file path and confirmation message\n final_path = Path.cwd() / file_path if not file_path.is_absolute() else file_path\n return Message(text=f\"{confirmation} at {final_path}\")\n\n async def _save_to_aws(self) -> Message:\n \"\"\"Save file to AWS S3 using S3 functionality.\"\"\"\n # Validate AWS credentials\n if not getattr(self, \"aws_access_key_id\", None):\n msg = \"AWS Access Key ID is required for S3 storage\"\n raise ValueError(msg)\n if not getattr(self, \"aws_secret_access_key\", None):\n msg = \"AWS Secret Key is required for S3 storage\"\n raise ValueError(msg)\n if not getattr(self, \"bucket_name\", None):\n msg = \"S3 Bucket Name is required for S3 storage\"\n raise ValueError(msg)\n\n # Use S3 upload functionality\n try:\n import boto3\n except ImportError as e:\n msg = \"boto3 is not installed. Please install it using `uv pip install boto3`.\"\n raise ImportError(msg) from e\n\n # Create S3 client\n client_config = {\n \"aws_access_key_id\": self.aws_access_key_id,\n \"aws_secret_access_key\": self.aws_secret_access_key,\n }\n\n if hasattr(self, \"aws_region\") and self.aws_region:\n client_config[\"region_name\"] = self.aws_region\n\n s3_client = boto3.client(\"s3\", **client_config)\n\n # Extract content\n content = self._extract_content_for_upload()\n file_format = self._get_file_format_for_location(\"AWS\")\n\n # Generate file path\n file_path = f\"{self.file_name}.{file_format}\"\n if hasattr(self, \"s3_prefix\") and self.s3_prefix:\n file_path = f\"{self.s3_prefix.rstrip('/')}/{file_path}\"\n\n # Create temporary file\n import tempfile\n\n with tempfile.NamedTemporaryFile(mode=\"w\", suffix=f\".{file_format}\", delete=False) as temp_file:\n temp_file.write(content)\n temp_file_path = temp_file.name\n\n try:\n # Upload to S3\n s3_client.upload_file(temp_file_path, self.bucket_name, file_path)\n s3_url = f\"s3://{self.bucket_name}/{file_path}\"\n return Message(text=f\"File successfully uploaded to {s3_url}\")\n finally:\n # Clean up temp file\n if Path(temp_file_path).exists():\n Path(temp_file_path).unlink()\n\n async def _save_to_google_drive(self) -> Message:\n \"\"\"Save file to Google Drive using Google Drive functionality.\"\"\"\n # Validate Google Drive credentials\n if not getattr(self, \"service_account_key\", None):\n msg = \"GCP Credentials Secret Key is required for Google Drive storage\"\n raise ValueError(msg)\n if not getattr(self, \"folder_id\", None):\n msg = \"Google Drive Folder ID is required for Google Drive storage\"\n raise ValueError(msg)\n\n # Use Google Drive upload functionality\n try:\n import json\n import tempfile\n\n from google.oauth2 import service_account\n from googleapiclient.discovery import build\n from googleapiclient.http import MediaFileUpload\n except ImportError as e:\n msg = \"Google API client libraries are not installed. Please install them.\"\n raise ImportError(msg) from e\n\n # Parse credentials\n try:\n credentials_dict = json.loads(self.service_account_key)\n except json.JSONDecodeError as e:\n msg = f\"Invalid JSON in service account key: {e!s}\"\n raise ValueError(msg) from e\n\n # Create Google Drive service\n credentials = service_account.Credentials.from_service_account_info(\n credentials_dict, scopes=[\"https://www.googleapis.com/auth/drive.file\"]\n )\n drive_service = build(\"drive\", \"v3\", credentials=credentials)\n\n # Extract content and format\n content = self._extract_content_for_upload()\n file_format = self._get_file_format_for_location(\"Google Drive\")\n\n # Handle special Google Drive formats\n if file_format in [\"slides\", \"docs\"]:\n return await self._save_to_google_apps(drive_service, credentials, content, file_format)\n\n # Create temporary file\n file_path = f\"{self.file_name}.{file_format}\"\n with tempfile.NamedTemporaryFile(mode=\"w\", suffix=f\".{file_format}\", delete=False) as temp_file:\n temp_file.write(content)\n temp_file_path = temp_file.name\n\n try:\n # Upload to Google Drive\n file_metadata = {\"name\": file_path, \"parents\": [self.folder_id]}\n media = MediaFileUpload(temp_file_path, resumable=True)\n\n uploaded_file = drive_service.files().create(body=file_metadata, media_body=media, fields=\"id\").execute()\n\n file_id = uploaded_file.get(\"id\")\n file_url = f\"https://drive.google.com/file/d/{file_id}/view\"\n return Message(text=f\"File successfully uploaded to Google Drive: {file_url}\")\n finally:\n # Clean up temp file\n if Path(temp_file_path).exists():\n Path(temp_file_path).unlink()\n\n async def _save_to_google_apps(self, drive_service, credentials, content: str, app_type: str) -> Message:\n \"\"\"Save content to Google Apps (Slides or Docs).\"\"\"\n import time\n\n if app_type == \"slides\":\n from googleapiclient.discovery import build\n\n slides_service = build(\"slides\", \"v1\", credentials=credentials)\n\n file_metadata = {\n \"name\": self.file_name,\n \"mimeType\": \"application/vnd.google-apps.presentation\",\n \"parents\": [self.folder_id],\n }\n\n created_file = drive_service.files().create(body=file_metadata, fields=\"id\").execute()\n presentation_id = created_file[\"id\"]\n\n time.sleep(2) # Wait for file to be available # noqa: ASYNC251\n\n presentation = slides_service.presentations().get(presentationId=presentation_id).execute()\n slide_id = presentation[\"slides\"][0][\"objectId\"]\n\n # Add content to slide\n requests = [\n {\n \"createShape\": {\n \"objectId\": \"TextBox_01\",\n \"shapeType\": \"TEXT_BOX\",\n \"elementProperties\": {\n \"pageObjectId\": slide_id,\n \"size\": {\n \"height\": {\"magnitude\": 3000000, \"unit\": \"EMU\"},\n \"width\": {\"magnitude\": 6000000, \"unit\": \"EMU\"},\n },\n \"transform\": {\n \"scaleX\": 1,\n \"scaleY\": 1,\n \"translateX\": 1000000,\n \"translateY\": 1000000,\n \"unit\": \"EMU\",\n },\n },\n }\n },\n {\"insertText\": {\"objectId\": \"TextBox_01\", \"insertionIndex\": 0, \"text\": content}},\n ]\n\n slides_service.presentations().batchUpdate(\n presentationId=presentation_id, body={\"requests\": requests}\n ).execute()\n file_url = f\"https://docs.google.com/presentation/d/{presentation_id}/edit\"\n\n elif app_type == \"docs\":\n from googleapiclient.discovery import build\n\n docs_service = build(\"docs\", \"v1\", credentials=credentials)\n\n file_metadata = {\n \"name\": self.file_name,\n \"mimeType\": \"application/vnd.google-apps.document\",\n \"parents\": [self.folder_id],\n }\n\n created_file = drive_service.files().create(body=file_metadata, fields=\"id\").execute()\n document_id = created_file[\"id\"]\n\n time.sleep(2) # Wait for file to be available # noqa: ASYNC251\n\n # Add content to document\n requests = [{\"insertText\": {\"location\": {\"index\": 1}, \"text\": content}}]\n docs_service.documents().batchUpdate(documentId=document_id, body={\"requests\": requests}).execute()\n file_url = f\"https://docs.google.com/document/d/{document_id}/edit\"\n\n return Message(text=f\"File successfully created in Google {app_type.title()}: {file_url}\")\n\n def _extract_content_for_upload(self) -> str:\n \"\"\"Extract content from input for upload to cloud services.\"\"\"\n if self._get_input_type() == \"DataFrame\":\n return self.input.to_csv(index=False)\n if self._get_input_type() == \"Data\":\n if hasattr(self.input, \"data\") and self.input.data:\n if isinstance(self.input.data, dict):\n import json\n\n return json.dumps(self.input.data, indent=2, ensure_ascii=False)\n return str(self.input.data)\n return str(self.input)\n if self._get_input_type() == \"Message\":\n return str(self.input.text) if self.input.text else str(self.input)\n return str(self.input)\n" + "value": "import json\nfrom collections.abc import AsyncIterator, Iterator\nfrom pathlib import Path\n\nimport orjson\nimport pandas as pd\nfrom fastapi import UploadFile\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.custom import Component\nfrom lfx.inputs import SortableListInput\nfrom lfx.io import BoolInput, DropdownInput, HandleInput, SecretStrInput, StrInput\nfrom lfx.schema import Data, DataFrame, Message\nfrom lfx.services.deps import get_settings_service, get_storage_service, session_scope\nfrom lfx.template.field.base import Output\nfrom lfx.utils.validate_cloud import is_astra_cloud_environment\n\n\ndef _get_storage_location_options():\n \"\"\"Get storage location options, filtering out Local if in Astra cloud environment.\"\"\"\n all_options = [{\"name\": \"AWS\", \"icon\": \"Amazon\"}, {\"name\": \"Google Drive\", \"icon\": \"google\"}]\n if is_astra_cloud_environment():\n return all_options\n return [{\"name\": \"Local\", \"icon\": \"hard-drive\"}, *all_options]\n\n\nclass SaveToFileComponent(Component):\n display_name = \"Write File\"\n description = \"Save data to local file, AWS S3, or Google Drive in the selected format.\"\n documentation: str = \"https://docs.langflow.org/write-file\"\n icon = \"file-text\"\n name = \"SaveToFile\"\n\n # File format options for different storage types\n LOCAL_DATA_FORMAT_CHOICES = [\"csv\", \"excel\", \"json\", \"markdown\"]\n LOCAL_MESSAGE_FORMAT_CHOICES = [\"txt\", \"json\", \"markdown\"]\n AWS_FORMAT_CHOICES = [\n \"txt\",\n \"json\",\n \"csv\",\n \"xml\",\n \"html\",\n \"md\",\n \"yaml\",\n \"log\",\n \"tsv\",\n \"jsonl\",\n \"parquet\",\n \"xlsx\",\n \"zip\",\n ]\n GDRIVE_FORMAT_CHOICES = [\"txt\", \"json\", \"csv\", \"xlsx\", \"slides\", \"docs\", \"jpg\", \"mp3\"]\n\n inputs = [\n # Storage location selection\n SortableListInput(\n name=\"storage_location\",\n display_name=\"Storage Location\",\n placeholder=\"Select Location\",\n info=\"Choose where to save the file.\",\n options=_get_storage_location_options(),\n real_time_refresh=True,\n limit=1,\n ),\n # Common inputs\n HandleInput(\n name=\"input\",\n display_name=\"File Content\",\n info=\"The input to save.\",\n dynamic=True,\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n StrInput(\n name=\"file_name\",\n display_name=\"File Name\",\n info=\"Name file will be saved as (without extension).\",\n required=True,\n show=False,\n tool_mode=True,\n ),\n BoolInput(\n name=\"append_mode\",\n display_name=\"Append\",\n info=(\n \"Append to file if it exists (only for Local storage with plain text formats). \"\n \"Not supported for cloud storage (AWS/Google Drive).\"\n ),\n value=False,\n show=False,\n ),\n # Format inputs (dynamic based on storage location)\n DropdownInput(\n name=\"local_format\",\n display_name=\"File Format\",\n options=list(dict.fromkeys(LOCAL_DATA_FORMAT_CHOICES + LOCAL_MESSAGE_FORMAT_CHOICES)),\n info=\"Select the file format for local storage.\",\n value=\"json\",\n show=False,\n ),\n DropdownInput(\n name=\"aws_format\",\n display_name=\"File Format\",\n options=AWS_FORMAT_CHOICES,\n info=\"Select the file format for AWS S3 storage.\",\n value=\"txt\",\n show=False,\n ),\n DropdownInput(\n name=\"gdrive_format\",\n display_name=\"File Format\",\n options=GDRIVE_FORMAT_CHOICES,\n info=\"Select the file format for Google Drive storage.\",\n value=\"txt\",\n show=False,\n ),\n # AWS S3 specific inputs\n SecretStrInput(\n name=\"aws_access_key_id\",\n display_name=\"AWS Access Key ID\",\n info=\"AWS Access key ID.\",\n show=False,\n advanced=True,\n ),\n SecretStrInput(\n name=\"aws_secret_access_key\",\n display_name=\"AWS Secret Key\",\n info=\"AWS Secret Key.\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"bucket_name\",\n display_name=\"S3 Bucket Name\",\n info=\"Enter the name of the S3 bucket.\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"aws_region\",\n display_name=\"AWS Region\",\n info=\"AWS region (e.g., us-east-1, eu-west-1).\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"s3_prefix\",\n display_name=\"S3 Prefix\",\n info=\"Prefix for all files in S3.\",\n show=False,\n advanced=True,\n ),\n # Google Drive specific inputs\n SecretStrInput(\n name=\"service_account_key\",\n display_name=\"GCP Credentials Secret Key\",\n info=\"Your Google Cloud Platform service account JSON key as a secret string (complete JSON content).\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"folder_id\",\n display_name=\"Google Drive Folder ID\",\n info=(\n \"The Google Drive folder ID where the file will be uploaded. \"\n \"The folder must be shared with the service account email.\"\n ),\n required=True,\n show=False,\n advanced=True,\n ),\n ]\n\n outputs = [Output(display_name=\"File Path\", name=\"message\", method=\"save_to_file\")]\n\n def update_build_config(self, build_config, field_value, field_name=None):\n \"\"\"Update build configuration to show/hide fields based on storage location selection.\"\"\"\n # Update options dynamically based on cloud environment\n # This ensures options are refreshed when build_config is updated\n if \"storage_location\" in build_config:\n updated_options = _get_storage_location_options()\n build_config[\"storage_location\"][\"options\"] = updated_options\n\n if field_name != \"storage_location\":\n return build_config\n\n # Extract selected storage location\n selected = [location[\"name\"] for location in field_value] if isinstance(field_value, list) else []\n\n # Hide all dynamic fields first\n dynamic_fields = [\n \"file_name\", # Common fields (input is always visible)\n \"append_mode\",\n \"local_format\",\n \"aws_format\",\n \"gdrive_format\",\n \"aws_access_key_id\",\n \"aws_secret_access_key\",\n \"bucket_name\",\n \"aws_region\",\n \"s3_prefix\",\n \"service_account_key\",\n \"folder_id\",\n ]\n\n for f_name in dynamic_fields:\n if f_name in build_config:\n build_config[f_name][\"show\"] = False\n\n # Show fields based on selected storage location\n if len(selected) == 1:\n location = selected[0]\n\n # Show file_name when any storage location is selected\n if \"file_name\" in build_config:\n build_config[\"file_name\"][\"show\"] = True\n\n # Show append_mode only for Local storage (not supported for cloud storage)\n if \"append_mode\" in build_config:\n build_config[\"append_mode\"][\"show\"] = location == \"Local\"\n\n if location == \"Local\":\n if \"local_format\" in build_config:\n build_config[\"local_format\"][\"show\"] = True\n\n elif location == \"AWS\":\n aws_fields = [\n \"aws_format\",\n \"aws_access_key_id\",\n \"aws_secret_access_key\",\n \"bucket_name\",\n \"aws_region\",\n \"s3_prefix\",\n ]\n for f_name in aws_fields:\n if f_name in build_config:\n build_config[f_name][\"show\"] = True\n\n elif location == \"Google Drive\":\n gdrive_fields = [\"gdrive_format\", \"service_account_key\", \"folder_id\"]\n for f_name in gdrive_fields:\n if f_name in build_config:\n build_config[f_name][\"show\"] = True\n\n return build_config\n\n async def save_to_file(self) -> Message:\n \"\"\"Save the input to a file and upload it, returning a confirmation message.\"\"\"\n # Validate inputs\n if not self.file_name:\n msg = \"File name must be provided.\"\n raise ValueError(msg)\n if not self._get_input_type():\n msg = \"Input type is not set.\"\n raise ValueError(msg)\n\n # Get selected storage location\n storage_location = self._get_selected_storage_location()\n if not storage_location:\n msg = \"Storage location must be selected.\"\n raise ValueError(msg)\n\n # Check if Local storage is disabled in cloud environment\n if storage_location == \"Local\" and is_astra_cloud_environment():\n msg = \"Local storage is not available in cloud environment. Please use AWS or Google Drive.\"\n raise ValueError(msg)\n\n # Route to appropriate save method based on storage location\n if storage_location == \"Local\":\n return await self._save_to_local()\n if storage_location == \"AWS\":\n return await self._save_to_aws()\n if storage_location == \"Google Drive\":\n return await self._save_to_google_drive()\n msg = f\"Unsupported storage location: {storage_location}\"\n raise ValueError(msg)\n\n def _get_input_type(self) -> str:\n \"\"\"Determine the input type based on the provided input.\"\"\"\n # Use exact type checking (type() is) instead of isinstance() to avoid inheritance issues.\n # Since Message inherits from Data, isinstance(message, Data) would return True for Message objects,\n # causing Message inputs to be incorrectly identified as Data type.\n if type(self.input) is DataFrame:\n return \"DataFrame\"\n if type(self.input) is Message:\n return \"Message\"\n if type(self.input) is Data:\n return \"Data\"\n msg = f\"Unsupported input type: {type(self.input)}\"\n raise ValueError(msg)\n\n def _get_default_format(self) -> str:\n \"\"\"Return the default file format based on input type.\"\"\"\n if self._get_input_type() == \"DataFrame\":\n return \"csv\"\n if self._get_input_type() == \"Data\":\n return \"json\"\n if self._get_input_type() == \"Message\":\n return \"json\"\n return \"json\" # Fallback\n\n def _adjust_file_path_with_format(self, path: Path, fmt: str) -> Path:\n \"\"\"Adjust the file path to include the correct extension.\"\"\"\n file_extension = path.suffix.lower().lstrip(\".\")\n if fmt == \"excel\":\n return Path(f\"{path}.xlsx\").expanduser() if file_extension not in [\"xlsx\", \"xls\"] else path\n return Path(f\"{path}.{fmt}\").expanduser() if file_extension != fmt else path\n\n def _is_plain_text_format(self, fmt: str) -> bool:\n \"\"\"Check if a file format is plain text (supports appending).\"\"\"\n plain_text_formats = [\"txt\", \"json\", \"markdown\", \"md\", \"csv\", \"xml\", \"html\", \"yaml\", \"log\", \"tsv\", \"jsonl\"]\n return fmt.lower() in plain_text_formats\n\n async def _upload_file(self, file_path: Path) -> None:\n \"\"\"Upload the saved file using the upload_user_file service.\"\"\"\n from langflow.api.v2.files import upload_user_file\n from langflow.services.database.models.user.crud import get_user_by_id\n\n # Ensure the file exists\n if not file_path.exists():\n msg = f\"File not found: {file_path}\"\n raise FileNotFoundError(msg)\n\n # Upload the file - always use append=False because the local file already contains\n # the correct content (either new or appended locally)\n with file_path.open(\"rb\") as f:\n async with session_scope() as db:\n if not self.user_id:\n msg = \"User ID is required for file saving.\"\n raise ValueError(msg)\n current_user = await get_user_by_id(db, self.user_id)\n\n await upload_user_file(\n file=UploadFile(filename=file_path.name, file=f, size=file_path.stat().st_size),\n session=db,\n current_user=current_user,\n storage_service=get_storage_service(),\n settings_service=get_settings_service(),\n append=False,\n )\n\n def _save_dataframe(self, dataframe: DataFrame, path: Path, fmt: str) -> str:\n \"\"\"Save a DataFrame to the specified file format.\"\"\"\n append_mode = getattr(self, \"append_mode\", False)\n should_append = append_mode and path.exists() and self._is_plain_text_format(fmt)\n\n if fmt == \"csv\":\n dataframe.to_csv(path, index=False, mode=\"a\" if should_append else \"w\", header=not should_append)\n elif fmt == \"excel\":\n dataframe.to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n if should_append:\n # Read and parse existing JSON\n existing_data = []\n try:\n existing_content = path.read_text(encoding=\"utf-8\").strip()\n if existing_content:\n parsed = json.loads(existing_content)\n # Handle case where existing content is a single object\n if isinstance(parsed, dict):\n existing_data = [parsed]\n elif isinstance(parsed, list):\n existing_data = parsed\n except (json.JSONDecodeError, FileNotFoundError):\n # Treat parse errors or missing file as empty array\n existing_data = []\n\n # Append new data\n new_records = json.loads(dataframe.to_json(orient=\"records\"))\n existing_data.extend(new_records)\n\n # Write back as a single JSON array\n path.write_text(json.dumps(existing_data, indent=2), encoding=\"utf-8\")\n else:\n dataframe.to_json(path, orient=\"records\", indent=2)\n elif fmt == \"markdown\":\n content = dataframe.to_markdown(index=False)\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\\n\" + content, encoding=\"utf-8\")\n else:\n path.write_text(content, encoding=\"utf-8\")\n else:\n msg = f\"Unsupported DataFrame format: {fmt}\"\n raise ValueError(msg)\n action = \"appended to\" if should_append else \"saved successfully as\"\n return f\"DataFrame {action} '{path}'\"\n\n def _save_data(self, data: Data, path: Path, fmt: str) -> str:\n \"\"\"Save a Data object to the specified file format.\"\"\"\n append_mode = getattr(self, \"append_mode\", False)\n should_append = append_mode and path.exists() and self._is_plain_text_format(fmt)\n\n if fmt == \"csv\":\n pd.DataFrame(data.data).to_csv(\n path,\n index=False,\n mode=\"a\" if should_append else \"w\",\n header=not should_append,\n )\n elif fmt == \"excel\":\n pd.DataFrame(data.data).to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n new_data = jsonable_encoder(data.data)\n if should_append:\n # Read and parse existing JSON\n existing_data = []\n try:\n existing_content = path.read_text(encoding=\"utf-8\").strip()\n if existing_content:\n parsed = json.loads(existing_content)\n # Handle case where existing content is a single object\n if isinstance(parsed, dict):\n existing_data = [parsed]\n elif isinstance(parsed, list):\n existing_data = parsed\n except (json.JSONDecodeError, FileNotFoundError):\n # Treat parse errors or missing file as empty array\n existing_data = []\n\n # Append new data\n if isinstance(new_data, list):\n existing_data.extend(new_data)\n else:\n existing_data.append(new_data)\n\n # Write back as a single JSON array\n path.write_text(json.dumps(existing_data, indent=2), encoding=\"utf-8\")\n else:\n content = orjson.dumps(new_data, option=orjson.OPT_INDENT_2).decode(\"utf-8\")\n path.write_text(content, encoding=\"utf-8\")\n elif fmt == \"markdown\":\n content = pd.DataFrame(data.data).to_markdown(index=False)\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\\n\" + content, encoding=\"utf-8\")\n else:\n path.write_text(content, encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Data format: {fmt}\"\n raise ValueError(msg)\n action = \"appended to\" if should_append else \"saved successfully as\"\n return f\"Data {action} '{path}'\"\n\n async def _save_message(self, message: Message, path: Path, fmt: str) -> str:\n \"\"\"Save a Message to the specified file format, handling async iterators.\"\"\"\n content = \"\"\n if message.text is None:\n content = \"\"\n elif isinstance(message.text, AsyncIterator):\n async for item in message.text:\n content += str(item) + \" \"\n content = content.strip()\n elif isinstance(message.text, Iterator):\n content = \" \".join(str(item) for item in message.text)\n else:\n content = str(message.text)\n\n append_mode = getattr(self, \"append_mode\", False)\n should_append = append_mode and path.exists() and self._is_plain_text_format(fmt)\n\n if fmt == \"txt\":\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\" + content, encoding=\"utf-8\")\n else:\n path.write_text(content, encoding=\"utf-8\")\n elif fmt == \"json\":\n new_message = {\"message\": content}\n if should_append:\n # Read and parse existing JSON\n existing_data = []\n try:\n existing_content = path.read_text(encoding=\"utf-8\").strip()\n if existing_content:\n parsed = json.loads(existing_content)\n # Handle case where existing content is a single object\n if isinstance(parsed, dict):\n existing_data = [parsed]\n elif isinstance(parsed, list):\n existing_data = parsed\n except (json.JSONDecodeError, FileNotFoundError):\n # Treat parse errors or missing file as empty array\n existing_data = []\n\n # Append new message\n existing_data.append(new_message)\n\n # Write back as a single JSON array\n path.write_text(json.dumps(existing_data, indent=2), encoding=\"utf-8\")\n else:\n path.write_text(json.dumps(new_message, indent=2), encoding=\"utf-8\")\n elif fmt == \"markdown\":\n md_content = f\"**Message:**\\n\\n{content}\"\n if should_append:\n path.write_text(path.read_text(encoding=\"utf-8\") + \"\\n\\n\" + md_content, encoding=\"utf-8\")\n else:\n path.write_text(md_content, encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Message format: {fmt}\"\n raise ValueError(msg)\n action = \"appended to\" if should_append else \"saved successfully as\"\n return f\"Message {action} '{path}'\"\n\n def _get_selected_storage_location(self) -> str:\n \"\"\"Get the selected storage location from the SortableListInput.\"\"\"\n if hasattr(self, \"storage_location\") and self.storage_location:\n if isinstance(self.storage_location, list) and len(self.storage_location) > 0:\n return self.storage_location[0].get(\"name\", \"\")\n if isinstance(self.storage_location, dict):\n return self.storage_location.get(\"name\", \"\")\n return \"\"\n\n def _get_file_format_for_location(self, location: str) -> str:\n \"\"\"Get the appropriate file format based on storage location.\"\"\"\n if location == \"Local\":\n return getattr(self, \"local_format\", None) or self._get_default_format()\n if location == \"AWS\":\n return getattr(self, \"aws_format\", \"txt\")\n if location == \"Google Drive\":\n return getattr(self, \"gdrive_format\", \"txt\")\n return self._get_default_format()\n\n async def _save_to_local(self) -> Message:\n \"\"\"Save file to local storage (original functionality).\"\"\"\n file_format = self._get_file_format_for_location(\"Local\")\n\n # Validate file format based on input type\n allowed_formats = (\n self.LOCAL_MESSAGE_FORMAT_CHOICES if self._get_input_type() == \"Message\" else self.LOCAL_DATA_FORMAT_CHOICES\n )\n if file_format not in allowed_formats:\n msg = f\"Invalid file format '{file_format}' for {self._get_input_type()}. Allowed: {allowed_formats}\"\n raise ValueError(msg)\n\n # Prepare file path\n file_path = Path(self.file_name).expanduser()\n if not file_path.parent.exists():\n file_path.parent.mkdir(parents=True, exist_ok=True)\n file_path = self._adjust_file_path_with_format(file_path, file_format)\n\n # Save the input to file based on type\n if self._get_input_type() == \"DataFrame\":\n confirmation = self._save_dataframe(self.input, file_path, file_format)\n elif self._get_input_type() == \"Data\":\n confirmation = self._save_data(self.input, file_path, file_format)\n elif self._get_input_type() == \"Message\":\n confirmation = await self._save_message(self.input, file_path, file_format)\n else:\n msg = f\"Unsupported input type: {self._get_input_type()}\"\n raise ValueError(msg)\n\n # Upload the saved file\n await self._upload_file(file_path)\n\n # Return the final file path and confirmation message\n final_path = Path.cwd() / file_path if not file_path.is_absolute() else file_path\n return Message(text=f\"{confirmation} at {final_path}\")\n\n async def _save_to_aws(self) -> Message:\n \"\"\"Save file to AWS S3 using S3 functionality.\"\"\"\n # Validate AWS credentials\n if not getattr(self, \"aws_access_key_id\", None):\n msg = \"AWS Access Key ID is required for S3 storage\"\n raise ValueError(msg)\n if not getattr(self, \"aws_secret_access_key\", None):\n msg = \"AWS Secret Key is required for S3 storage\"\n raise ValueError(msg)\n if not getattr(self, \"bucket_name\", None):\n msg = \"S3 Bucket Name is required for S3 storage\"\n raise ValueError(msg)\n\n # Use S3 upload functionality\n try:\n import boto3\n except ImportError as e:\n msg = \"boto3 is not installed. Please install it using `uv pip install boto3`.\"\n raise ImportError(msg) from e\n\n # Create S3 client\n client_config = {\n \"aws_access_key_id\": self.aws_access_key_id,\n \"aws_secret_access_key\": self.aws_secret_access_key,\n }\n\n if hasattr(self, \"aws_region\") and self.aws_region:\n client_config[\"region_name\"] = self.aws_region\n\n s3_client = boto3.client(\"s3\", **client_config)\n\n # Extract content\n content = self._extract_content_for_upload()\n file_format = self._get_file_format_for_location(\"AWS\")\n\n # Generate file path\n file_path = f\"{self.file_name}.{file_format}\"\n if hasattr(self, \"s3_prefix\") and self.s3_prefix:\n file_path = f\"{self.s3_prefix.rstrip('/')}/{file_path}\"\n\n # Create temporary file\n import tempfile\n\n with tempfile.NamedTemporaryFile(\n mode=\"w\", encoding=\"utf-8\", suffix=f\".{file_format}\", delete=False\n ) as temp_file:\n temp_file.write(content)\n temp_file_path = temp_file.name\n\n try:\n # Upload to S3\n s3_client.upload_file(temp_file_path, self.bucket_name, file_path)\n s3_url = f\"s3://{self.bucket_name}/{file_path}\"\n return Message(text=f\"File successfully uploaded to {s3_url}\")\n finally:\n # Clean up temp file\n if Path(temp_file_path).exists():\n Path(temp_file_path).unlink()\n\n async def _save_to_google_drive(self) -> Message:\n \"\"\"Save file to Google Drive using Google Drive functionality.\"\"\"\n # Validate Google Drive credentials\n if not getattr(self, \"service_account_key\", None):\n msg = \"GCP Credentials Secret Key is required for Google Drive storage\"\n raise ValueError(msg)\n if not getattr(self, \"folder_id\", None):\n msg = \"Google Drive Folder ID is required for Google Drive storage\"\n raise ValueError(msg)\n\n # Use Google Drive upload functionality\n try:\n import json\n import tempfile\n\n from google.oauth2 import service_account\n from googleapiclient.discovery import build\n from googleapiclient.http import MediaFileUpload\n except ImportError as e:\n msg = \"Google API client libraries are not installed. Please install them.\"\n raise ImportError(msg) from e\n\n # Parse credentials with multiple fallback strategies\n credentials_dict = None\n parse_errors = []\n\n # Strategy 1: Parse as-is with strict=False to allow control characters\n try:\n credentials_dict = json.loads(self.service_account_key, strict=False)\n except json.JSONDecodeError as e:\n parse_errors.append(f\"Standard parse: {e!s}\")\n\n # Strategy 2: Strip whitespace and try again\n if credentials_dict is None:\n try:\n cleaned_key = self.service_account_key.strip()\n credentials_dict = json.loads(cleaned_key, strict=False)\n except json.JSONDecodeError as e:\n parse_errors.append(f\"Stripped parse: {e!s}\")\n\n # Strategy 3: Check if it's double-encoded (JSON string of a JSON string)\n if credentials_dict is None:\n try:\n decoded_once = json.loads(self.service_account_key, strict=False)\n if isinstance(decoded_once, str):\n credentials_dict = json.loads(decoded_once, strict=False)\n else:\n credentials_dict = decoded_once\n except json.JSONDecodeError as e:\n parse_errors.append(f\"Double-encoded parse: {e!s}\")\n\n # Strategy 4: Try to fix common issues with newlines in the private_key field\n if credentials_dict is None:\n try:\n # Replace literal \\n with actual newlines which is common in pasted JSON\n fixed_key = self.service_account_key.replace(\"\\\\n\", \"\\n\")\n credentials_dict = json.loads(fixed_key, strict=False)\n except json.JSONDecodeError as e:\n parse_errors.append(f\"Newline-fixed parse: {e!s}\")\n\n if credentials_dict is None:\n error_details = \"; \".join(parse_errors)\n msg = (\n f\"Unable to parse service account key JSON. Tried multiple strategies: {error_details}. \"\n \"Please ensure you've copied the entire JSON content from your service account key file. \"\n \"The JSON should start with '{' and contain fields like 'type', 'project_id', 'private_key', etc.\"\n )\n raise ValueError(msg)\n\n # Create Google Drive service with appropriate scopes\n # Use drive scope for folder access, file scope is too restrictive for folder verification\n credentials = service_account.Credentials.from_service_account_info(\n credentials_dict, scopes=[\"https://www.googleapis.com/auth/drive\"]\n )\n drive_service = build(\"drive\", \"v3\", credentials=credentials)\n\n # Extract content and format\n content = self._extract_content_for_upload()\n file_format = self._get_file_format_for_location(\"Google Drive\")\n\n # Handle special Google Drive formats\n if file_format in [\"slides\", \"docs\"]:\n return await self._save_to_google_apps(drive_service, credentials, content, file_format)\n\n # Create temporary file\n file_path = f\"{self.file_name}.{file_format}\"\n with tempfile.NamedTemporaryFile(\n mode=\"w\",\n encoding=\"utf-8\",\n suffix=f\".{file_format}\",\n delete=False,\n ) as temp_file:\n temp_file.write(content)\n temp_file_path = temp_file.name\n\n try:\n # Upload to Google Drive\n # Note: We skip explicit folder verification since it requires broader permissions.\n # If the folder doesn't exist or isn't accessible, the create() call will fail with a clear error.\n file_metadata = {\"name\": file_path, \"parents\": [self.folder_id]}\n media = MediaFileUpload(temp_file_path, resumable=True)\n\n try:\n uploaded_file = (\n drive_service.files().create(body=file_metadata, media_body=media, fields=\"id\").execute()\n )\n except Exception as e:\n msg = (\n f\"Unable to upload file to Google Drive folder '{self.folder_id}'. \"\n f\"Error: {e!s}. \"\n \"Please ensure: 1) The folder ID is correct, 2) The folder exists, \"\n \"3) The service account has been granted access to this folder.\"\n )\n raise ValueError(msg) from e\n\n file_id = uploaded_file.get(\"id\")\n file_url = f\"https://drive.google.com/file/d/{file_id}/view\"\n return Message(text=f\"File successfully uploaded to Google Drive: {file_url}\")\n finally:\n # Clean up temp file\n if Path(temp_file_path).exists():\n Path(temp_file_path).unlink()\n\n async def _save_to_google_apps(self, drive_service, credentials, content: str, app_type: str) -> Message:\n \"\"\"Save content to Google Apps (Slides or Docs).\"\"\"\n import time\n\n if app_type == \"slides\":\n from googleapiclient.discovery import build\n\n slides_service = build(\"slides\", \"v1\", credentials=credentials)\n\n file_metadata = {\n \"name\": self.file_name,\n \"mimeType\": \"application/vnd.google-apps.presentation\",\n \"parents\": [self.folder_id],\n }\n\n created_file = drive_service.files().create(body=file_metadata, fields=\"id\").execute()\n presentation_id = created_file[\"id\"]\n\n time.sleep(2) # Wait for file to be available # noqa: ASYNC251\n\n presentation = slides_service.presentations().get(presentationId=presentation_id).execute()\n slide_id = presentation[\"slides\"][0][\"objectId\"]\n\n # Add content to slide\n requests = [\n {\n \"createShape\": {\n \"objectId\": \"TextBox_01\",\n \"shapeType\": \"TEXT_BOX\",\n \"elementProperties\": {\n \"pageObjectId\": slide_id,\n \"size\": {\n \"height\": {\"magnitude\": 3000000, \"unit\": \"EMU\"},\n \"width\": {\"magnitude\": 6000000, \"unit\": \"EMU\"},\n },\n \"transform\": {\n \"scaleX\": 1,\n \"scaleY\": 1,\n \"translateX\": 1000000,\n \"translateY\": 1000000,\n \"unit\": \"EMU\",\n },\n },\n }\n },\n {\"insertText\": {\"objectId\": \"TextBox_01\", \"insertionIndex\": 0, \"text\": content}},\n ]\n\n slides_service.presentations().batchUpdate(\n presentationId=presentation_id, body={\"requests\": requests}\n ).execute()\n file_url = f\"https://docs.google.com/presentation/d/{presentation_id}/edit\"\n\n elif app_type == \"docs\":\n from googleapiclient.discovery import build\n\n docs_service = build(\"docs\", \"v1\", credentials=credentials)\n\n file_metadata = {\n \"name\": self.file_name,\n \"mimeType\": \"application/vnd.google-apps.document\",\n \"parents\": [self.folder_id],\n }\n\n created_file = drive_service.files().create(body=file_metadata, fields=\"id\").execute()\n document_id = created_file[\"id\"]\n\n time.sleep(2) # Wait for file to be available # noqa: ASYNC251\n\n # Add content to document\n requests = [{\"insertText\": {\"location\": {\"index\": 1}, \"text\": content}}]\n docs_service.documents().batchUpdate(documentId=document_id, body={\"requests\": requests}).execute()\n file_url = f\"https://docs.google.com/document/d/{document_id}/edit\"\n\n return Message(text=f\"File successfully created in Google {app_type.title()}: {file_url}\")\n\n def _extract_content_for_upload(self) -> str:\n \"\"\"Extract content from input for upload to cloud services.\"\"\"\n if self._get_input_type() == \"DataFrame\":\n return self.input.to_csv(index=False)\n if self._get_input_type() == \"Data\":\n if hasattr(self.input, \"data\") and self.input.data:\n if isinstance(self.input.data, dict):\n import json\n\n return json.dumps(self.input.data, indent=2, ensure_ascii=False)\n return str(self.input.data)\n return str(self.input)\n if self._get_input_type() == \"Message\":\n return str(self.input.text) if self.input.text else str(self.input)\n return str(self.input)\n" }, "file_name": { "_input_type": "StrInput", @@ -2162,7 +1875,7 @@ "load_from_db": false, "name": "folder_id", "placeholder": "", - "required": false, + "required": true, "show": false, "title_case": false, "tool_mode": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json b/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json index 8a74c6397299..ab6938c6d18f 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Nvidia Remix.json @@ -511,7 +511,7 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -586,7 +586,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -810,13 +810,9 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -824,6 +820,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -894,71 +894,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": false, "name": "api_key", @@ -970,27 +911,6 @@ "type": "str", "value": "" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1007,7 +927,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1116,137 +1036,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1266,27 +1091,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1349,47 +1153,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1415,56 +1178,6 @@ "type": "str", "value": "You are a helpful assistant that must use tools to answer questions and perform tasks regarding RTX Remix.\n\nBefore " }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -1882,39 +1595,19 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "d3ef27c15b29", + "code_hash": "834cb8f36272", "dependencies": { "dependencies": [ - { - "name": "requests", - "version": "2.32.5" - }, - { - "name": "ibm_watsonx_ai", - "version": "1.4.7" - }, - { - "name": "langchain_openai", - "version": "0.3.23" - }, { "name": "lfx", "version": null }, { - "name": "langchain_ollama", - "version": "0.3.10" - }, - { - "name": "langchain_community", - "version": "0.3.21" - }, - { - "name": "langchain_ibm", - "version": "0.3.20" + "name": "langchain_core", + "version": "0.3.80" } ], - "total_dependencies": 7 + "total_dependencies": 2 }, "module": "lfx.components.models_and_agents.embedding_model.EmbeddingModelComponent" }, @@ -1965,7 +1658,7 @@ "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, "info": "Model Provider API key", "input_types": [], @@ -1974,7 +1667,7 @@ "password": true, "placeholder": "", "real_time_refresh": true, - "required": true, + "required": false, "show": true, "title_case": false, "type": "str", @@ -2046,7 +1739,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom ibm_watsonx_ai.metanames import EmbedTextParamsMetaNames\nfrom langchain_openai import OpenAIEmbeddings\n\nfrom lfx.base.embeddings.embeddings_class import EmbeddingsWithModels\nfrom lfx.base.embeddings.model import LCEmbeddingsModel\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES\nfrom lfx.base.models.watsonx_constants import (\n IBM_WATSONX_URLS,\n WATSONX_EMBEDDING_MODEL_NAMES,\n)\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageTextInput,\n SecretStrInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"embedding\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass EmbeddingModelComponent(LCEmbeddingsModel):\n display_name = \"Embedding Model\"\n description = \"Generate embeddings using a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-embedding-models\"\n icon = \"binary\"\n name = \"EmbeddingModel\"\n category = \"models\"\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Ollama\", \"IBM watsonx.ai\"],\n value=\"OpenAI\",\n info=\"Select the embedding model provider\",\n real_time_refresh=True,\n options_metadata=[{\"icon\": \"OpenAI\"}, {\"icon\": \"Ollama\"}, {\"icon\": \"WatsonxAI\"}],\n ),\n MessageTextInput(\n name=\"api_base\",\n display_name=\"API Base URL\",\n info=\"Base URL for the API. Leave empty for default.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"model\",\n display_name=\"Model Name\",\n options=OPENAI_EMBEDDING_MODEL_NAMES,\n value=OPENAI_EMBEDDING_MODEL_NAMES[0],\n info=\"Select the embedding model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=True,\n show=True,\n real_time_refresh=True,\n ),\n # Watson-specific inputs\n MessageTextInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"IBM watsonx.ai Project ID (required for IBM watsonx.ai)\",\n show=False,\n ),\n IntInput(\n name=\"dimensions\",\n display_name=\"Dimensions\",\n info=\"The number of dimensions the resulting output embeddings should have. \"\n \"Only supported by certain models.\",\n advanced=True,\n ),\n IntInput(name=\"chunk_size\", display_name=\"Chunk Size\", advanced=True, value=1000),\n FloatInput(name=\"request_timeout\", display_name=\"Request Timeout\", advanced=True),\n IntInput(name=\"max_retries\", display_name=\"Max Retries\", advanced=True, value=3),\n BoolInput(name=\"show_progress_bar\", display_name=\"Show Progress Bar\", advanced=True),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n IntInput(\n name=\"truncate_input_tokens\",\n display_name=\"Truncate Input Tokens\",\n advanced=True,\n value=200,\n show=False,\n ),\n BoolInput(\n name=\"input_text\",\n display_name=\"Include the original text in the output\",\n value=True,\n advanced=True,\n show=False,\n ),\n BoolInput(\n name=\"fail_safe_mode\",\n display_name=\"Fail-Safe Mode\",\n value=False,\n advanced=True,\n info=\"When enabled, errors will be logged instead of raising exceptions. \"\n \"The component will return None on error.\",\n real_time_refresh=True,\n ),\n ]\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\n \"version\": \"2024-09-16\",\n \"filters\": \"function_embedding,!lifecycle_withdrawn:and\",\n }\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching models\")\n return WATSONX_EMBEDDING_MODEL_NAMES\n\n async def fetch_ollama_models(self) -> list[str]:\n try:\n return await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching models\")\n return []\n\n async def build_embeddings(self) -> Embeddings:\n provider = self.provider\n model = self.model\n api_key = self.api_key\n api_base = self.api_base\n base_url_ibm_watsonx = self.base_url_ibm_watsonx\n ollama_base_url = self.ollama_base_url\n dimensions = self.dimensions\n chunk_size = self.chunk_size\n request_timeout = self.request_timeout\n max_retries = self.max_retries\n show_progress_bar = self.show_progress_bar\n model_kwargs = self.model_kwargs or {}\n\n if provider == \"OpenAI\":\n if not api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n try:\n # Create the primary embedding instance\n embeddings_instance = OpenAIEmbeddings(\n model=model,\n dimensions=dimensions or None,\n base_url=api_base or None,\n api_key=api_key,\n chunk_size=chunk_size,\n max_retries=max_retries,\n timeout=request_timeout or None,\n show_progress_bar=show_progress_bar,\n model_kwargs=model_kwargs,\n )\n\n # Create dedicated instances for each available model\n available_models_dict = {}\n for model_name in OPENAI_EMBEDDING_MODEL_NAMES:\n available_models_dict[model_name] = OpenAIEmbeddings(\n model=model_name,\n dimensions=dimensions or None, # Use same dimensions config for all\n base_url=api_base or None,\n api_key=api_key,\n chunk_size=chunk_size,\n max_retries=max_retries,\n timeout=request_timeout or None,\n show_progress_bar=show_progress_bar,\n model_kwargs=model_kwargs,\n )\n\n return EmbeddingsWithModels(\n embeddings=embeddings_instance,\n available_models=available_models_dict,\n )\n except Exception as e:\n msg = f\"Failed to initialize OpenAI embeddings: {e}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise\n\n if provider == \"Ollama\":\n try:\n from langchain_ollama import OllamaEmbeddings\n except ImportError:\n try:\n from langchain_community.embeddings import OllamaEmbeddings\n except ImportError:\n msg = \"Please install langchain-ollama: pip install langchain-ollama\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ImportError(msg) from None\n\n try:\n transformed_base_url = transform_localhost_url(ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n final_base_url = transformed_base_url or \"http://localhost:11434\"\n\n # Create the primary embedding instance\n embeddings_instance = OllamaEmbeddings(\n model=model,\n base_url=final_base_url,\n **model_kwargs,\n )\n\n # Fetch available Ollama models\n available_model_names = await self.fetch_ollama_models()\n\n # Create dedicated instances for each available model\n available_models_dict = {}\n for model_name in available_model_names:\n available_models_dict[model_name] = OllamaEmbeddings(\n model=model_name,\n base_url=final_base_url,\n **model_kwargs,\n )\n\n return EmbeddingsWithModels(\n embeddings=embeddings_instance,\n available_models=available_models_dict,\n )\n except Exception as e:\n msg = f\"Failed to initialize Ollama embeddings: {e}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise\n\n if provider == \"IBM watsonx.ai\":\n try:\n from langchain_ibm import WatsonxEmbeddings\n except ImportError:\n msg = \"Please install langchain-ibm: pip install langchain-ibm\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ImportError(msg) from None\n\n if not api_key:\n msg = \"IBM watsonx.ai API key is required when using IBM watsonx.ai provider\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n project_id = self.project_id\n\n if not project_id:\n msg = \"Project ID is required for IBM watsonx.ai provider\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n try:\n from ibm_watsonx_ai import APIClient, Credentials\n\n final_url = base_url_ibm_watsonx or \"https://us-south.ml.cloud.ibm.com\"\n\n credentials = Credentials(\n api_key=self.api_key,\n url=final_url,\n )\n\n api_client = APIClient(credentials)\n\n params = {\n EmbedTextParamsMetaNames.TRUNCATE_INPUT_TOKENS: self.truncate_input_tokens,\n EmbedTextParamsMetaNames.RETURN_OPTIONS: {\"input_text\": self.input_text},\n }\n\n # Create the primary embedding instance\n embeddings_instance = WatsonxEmbeddings(\n model_id=model,\n params=params,\n watsonx_client=api_client,\n project_id=project_id,\n )\n\n # Fetch available IBM watsonx.ai models\n available_model_names = self.fetch_ibm_models(final_url)\n\n # Create dedicated instances for each available model\n available_models_dict = {}\n for model_name in available_model_names:\n available_models_dict[model_name] = WatsonxEmbeddings(\n model_id=model_name,\n params=params,\n watsonx_client=api_client,\n project_id=project_id,\n )\n\n return EmbeddingsWithModels(\n embeddings=embeddings_instance,\n available_models=available_models_dict,\n )\n except Exception as e:\n msg = f\"Failed to authenticate with IBM watsonx.ai: {e}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise\n\n msg = f\"Unknown provider: {provider}\"\n if self.fail_safe_mode:\n logger.error(msg)\n return None\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n # Handle fail_safe_mode changes first - set all required fields to False if enabled\n if field_name == \"fail_safe_mode\":\n if field_value: # If fail_safe_mode is enabled\n build_config[\"api_key\"][\"required\"] = False\n elif hasattr(self, \"provider\"):\n # If fail_safe_mode is disabled, restore required flags based on provider\n if self.provider in [\"OpenAI\", \"IBM watsonx.ai\"]:\n build_config[\"api_key\"][\"required\"] = True\n else: # Ollama\n build_config[\"api_key\"][\"required\"] = False\n\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model\"][\"options\"] = OPENAI_EMBEDDING_MODEL_NAMES\n build_config[\"model\"][\"value\"] = OPENAI_EMBEDDING_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n # Only set required=True if fail_safe_mode is not enabled\n build_config[\"api_key\"][\"required\"] = not (hasattr(self, \"fail_safe_mode\") and self.fail_safe_mode)\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"api_base\"][\"display_name\"] = \"OpenAI API Base URL\"\n build_config[\"api_base\"][\"advanced\"] = True\n build_config[\"api_base\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"truncate_input_tokens\"][\"show\"] = False\n build_config[\"input_text\"][\"show\"] = False\n elif field_value == \"Ollama\":\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await self.fetch_ollama_models()\n build_config[\"model\"][\"options\"] = models\n build_config[\"model\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n build_config[\"model\"][\"options\"] = []\n build_config[\"model\"][\"value\"] = \"\"\n else:\n build_config[\"model\"][\"options\"] = []\n build_config[\"model\"][\"value\"] = \"\"\n build_config[\"truncate_input_tokens\"][\"show\"] = False\n build_config[\"input_text\"][\"show\"] = False\n build_config[\"api_key\"][\"display_name\"] = \"API Key (Optional)\"\n build_config[\"api_key\"][\"required\"] = False\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"api_base\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model\"][\"options\"] = self.fetch_ibm_models(base_url=self.base_url_ibm_watsonx)\n build_config[\"model\"][\"value\"] = self.fetch_ibm_models(base_url=self.base_url_ibm_watsonx)[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM watsonx.ai API Key\"\n # Only set required=True if fail_safe_mode is not enabled\n build_config[\"api_key\"][\"required\"] = not (hasattr(self, \"fail_safe_mode\") and self.fail_safe_mode)\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"api_base\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"truncate_input_tokens\"][\"show\"] = True\n build_config[\"input_text\"][\"show\"] = True\n elif field_name == \"base_url_ibm_watsonx\":\n build_config[\"model\"][\"options\"] = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model\"][\"value\"] = self.fetch_ibm_models(base_url=field_value)[0]\n elif field_name == \"ollama_base_url\":\n # # Refresh Ollama models when base URL changes\n # if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n # Use field_value if provided, otherwise fall back to instance attribute\n ollama_url = self.ollama_base_url\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await self.fetch_ollama_models()\n build_config[\"model\"][\"options\"] = models\n build_config[\"model\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama embedding models.\")\n build_config[\"model\"][\"options\"] = []\n build_config[\"model\"][\"value\"] = \"\"\n\n elif field_name == \"model\" and self.provider == \"Ollama\":\n ollama_url = self.ollama_base_url\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await self.fetch_ollama_models()\n build_config[\"model\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama embedding models.\")\n build_config[\"model\"][\"options\"] = []\n\n return build_config\n" + "value": "from typing import Any\n\nfrom lfx.base.embeddings.model import LCEmbeddingsModel\nfrom lfx.base.models.unified_models import (\n get_api_key_for_provider,\n get_embedding_classes,\n get_embedding_model_options,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageTextInput,\n ModelInput,\n SecretStrInput,\n)\n\n\nclass EmbeddingModelComponent(LCEmbeddingsModel):\n display_name = \"Embedding Model\"\n description = \"Generate embeddings using a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-embedding-models\"\n icon = \"binary\"\n name = \"EmbeddingModel\"\n category = \"models\"\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"embedding_model_options\",\n get_options_func=get_embedding_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Embedding Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n model_type=\"embedding\",\n input_types=[\"Embeddings\"], # Override default to accept Embeddings instead of LanguageModel\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"api_base\",\n display_name=\"API Base URL\",\n info=\"Base URL for the API. Leave empty for default.\",\n advanced=True,\n ),\n # Watson-specific inputs\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"IBM watsonx.ai Project ID (required for IBM watsonx.ai)\",\n show=False,\n ),\n IntInput(\n name=\"dimensions\",\n display_name=\"Dimensions\",\n info=\"The number of dimensions the resulting output embeddings should have. \"\n \"Only supported by certain models.\",\n advanced=True,\n ),\n IntInput(\n name=\"chunk_size\",\n display_name=\"Chunk Size\",\n advanced=True,\n value=1000,\n ),\n FloatInput(\n name=\"request_timeout\",\n display_name=\"Request Timeout\",\n advanced=True,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n advanced=True,\n value=3,\n ),\n BoolInput(\n name=\"show_progress_bar\",\n display_name=\"Show Progress Bar\",\n advanced=True,\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n IntInput(\n name=\"truncate_input_tokens\",\n display_name=\"Truncate Input Tokens\",\n advanced=True,\n value=200,\n show=False,\n ),\n BoolInput(\n name=\"input_text\",\n display_name=\"Include the original text in the output\",\n value=True,\n advanced=True,\n show=False,\n ),\n ]\n\n def build_embeddings(self) -> Embeddings:\n \"\"\"Build and return an embeddings instance based on the selected model.\"\"\"\n # If an Embeddings object is directly connected, return it\n try:\n from langchain_core.embeddings import Embeddings as BaseEmbeddings\n\n if isinstance(self.model, BaseEmbeddings):\n return self.model\n except ImportError:\n pass\n\n # Safely extract model configuration\n if not self.model or not isinstance(self.model, list):\n msg = \"Model must be a non-empty list\"\n raise ValueError(msg)\n\n model = self.model[0]\n model_name = model.get(\"name\")\n provider = model.get(\"provider\")\n metadata = model.get(\"metadata\", {})\n\n # Get API key from user input or global variables\n api_key = get_api_key_for_provider(self.user_id, provider, self.api_key)\n\n # Validate required fields (Ollama doesn't require API key)\n if not api_key and provider != \"Ollama\":\n msg = (\n f\"{provider} API key is required. \"\n f\"Please provide it in the component or configure it globally as \"\n f\"{provider.upper().replace(' ', '_')}_API_KEY.\"\n )\n raise ValueError(msg)\n\n if not model_name:\n msg = \"Model name is required\"\n raise ValueError(msg)\n\n # Get embedding class\n embedding_class_name = metadata.get(\"embedding_class\")\n if not embedding_class_name:\n msg = f\"No embedding class defined in metadata for {model_name}\"\n raise ValueError(msg)\n\n embedding_class = get_embedding_classes().get(embedding_class_name)\n if not embedding_class:\n msg = f\"Unknown embedding class: {embedding_class_name}\"\n raise ValueError(msg)\n\n # Build kwargs using parameter mapping\n kwargs = self._build_kwargs(model, metadata)\n\n return embedding_class(**kwargs)\n\n def _build_kwargs(self, model: dict[str, Any], metadata: dict[str, Any]) -> dict[str, Any]:\n \"\"\"Build kwargs dictionary using parameter mapping.\"\"\"\n param_mapping = metadata.get(\"param_mapping\", {})\n if not param_mapping:\n msg = \"Parameter mapping not found in metadata\"\n raise ValueError(msg)\n\n kwargs = {}\n\n # Required parameters - handle both \"model\" and \"model_id\" (for watsonx)\n if \"model\" in param_mapping:\n kwargs[param_mapping[\"model\"]] = model.get(\"name\")\n elif \"model_id\" in param_mapping:\n kwargs[param_mapping[\"model_id\"]] = model.get(\"name\")\n if \"api_key\" in param_mapping:\n kwargs[param_mapping[\"api_key\"]] = get_api_key_for_provider(\n self.user_id,\n model.get(\"provider\"),\n self.api_key,\n )\n\n # Optional parameters with their values\n provider = model.get(\"provider\")\n optional_params = {\n \"api_base\": self.api_base if self.api_base else None,\n \"dimensions\": int(self.dimensions) if self.dimensions else None,\n \"chunk_size\": int(self.chunk_size) if self.chunk_size else None,\n \"request_timeout\": float(self.request_timeout) if self.request_timeout else None,\n \"max_retries\": int(self.max_retries) if self.max_retries else None,\n \"show_progress_bar\": self.show_progress_bar if hasattr(self, \"show_progress_bar\") else None,\n \"model_kwargs\": self.model_kwargs if self.model_kwargs else None,\n }\n\n # Watson-specific parameters\n if provider in {\"IBM WatsonX\", \"IBM watsonx.ai\"}:\n # Map base_url_ibm_watsonx to \"url\" parameter for watsonx\n if \"url\" in param_mapping:\n url_value = (\n self.base_url_ibm_watsonx\n if hasattr(self, \"base_url_ibm_watsonx\") and self.base_url_ibm_watsonx\n else \"https://us-south.ml.cloud.ibm.com\"\n )\n kwargs[param_mapping[\"url\"]] = url_value\n # Map project_id for watsonx\n if hasattr(self, \"project_id\") and self.project_id and \"project_id\" in param_mapping:\n kwargs[param_mapping[\"project_id\"]] = self.project_id\n\n # Ollama-specific parameters\n if provider == \"Ollama\" and \"base_url\" in param_mapping:\n # Map api_base to \"base_url\" parameter for Ollama\n base_url_value = self.api_base if hasattr(self, \"api_base\") and self.api_base else \"http://localhost:11434\"\n kwargs[param_mapping[\"base_url\"]] = base_url_value\n\n # Add optional parameters if they have values and are mapped\n for param_name, param_value in optional_params.items():\n if param_value is not None and param_name in param_mapping:\n # Special handling for request_timeout with Google provider\n if param_name == \"request_timeout\":\n if provider == \"Google\" and isinstance(param_value, (int, float)):\n kwargs[param_mapping[param_name]] = {\"timeout\": param_value}\n else:\n kwargs[param_mapping[param_name]] = param_value\n else:\n kwargs[param_mapping[param_name]] = param_value\n\n return kwargs\n" }, "dimensions": { "_input_type": "IntInput", @@ -2066,27 +1759,6 @@ "type": "int", "value": "" }, - "fail_safe_mode": { - "_input_type": "BoolInput", - "advanced": true, - "display_name": "Fail-Safe Mode", - "dynamic": false, - "info": "When enabled, errors will be logged instead of raising exceptions. The component will return None on error.", - "list": false, - "list_add_label": "Add More", - "name": "fail_safe_mode", - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "bool", - "value": false - }, "input_text": { "_input_type": "BoolInput", "advanced": true, @@ -2130,9 +1802,9 @@ "advanced": false, "combobox": false, "dialog_inputs": {}, - "display_name": "Model Name", + "display_name": "Embedding Model", "dynamic": false, - "info": "Select the embedding model to use", + "info": "Select your model provider", "name": "model", "options": [ "text-embedding-3-small", @@ -2141,7 +1813,7 @@ ], "options_metadata": [], "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "toggle": false, @@ -2168,32 +1840,6 @@ "type": "dict", "value": {} }, - "ollama_base_url": { - "_input_type": "MessageTextInput", - "advanced": false, - "display_name": "Ollama API URL", - "dynamic": false, - "info": "Endpoint of the Ollama API (Ollama only). Defaults to http://localhost:11434", - "input_types": [ - "Message" - ], - "list": false, - "list_add_label": "Add More", - "load_from_db": true, - "name": "ollama_base_url", - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_input": true, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "http://localhost:11434" - }, "project_id": { "_input_type": "MessageTextInput", "advanced": false, @@ -2219,45 +1865,6 @@ "type": "str", "value": "" }, - "provider": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": {}, - "info": "Select the embedding model provider", - "name": "provider", - "options": [ - "OpenAI", - "Ollama", - "IBM watsonx.ai" - ], - "options_metadata": [ - { - "icon": "OpenAI" - }, - { - "icon": "Ollama" - }, - { - "icon": "WatsonxAI" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "request_timeout": { "_input_type": "FloatInput", "advanced": true, @@ -2712,7 +2319,7 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "6d0bc49cd44e", + "code_hash": "39187e27d938", "dependencies": { "dependencies": [ { @@ -2772,7 +2379,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from __future__ import annotations\n\nimport asyncio\nimport json\nimport uuid\n\nfrom langchain_core.tools import StructuredTool # noqa: TC002\n\nfrom lfx.base.agents.utils import maybe_unflatten_dict, safe_cache_get, safe_cache_set\nfrom lfx.base.mcp.util import (\n MCPStdioClient,\n MCPStreamableHttpClient,\n create_input_schema_from_json_schema,\n update_tools,\n)\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.inputs.inputs import InputTypes # noqa: TC001\nfrom lfx.io import BoolInput, DropdownInput, McpInput, MessageTextInput, Output\nfrom lfx.io.schema import flatten_schema, schema_to_langflow_inputs\nfrom lfx.log.logger import logger\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.services.deps import get_settings_service, get_storage_service, session_scope\n\n\ndef resolve_mcp_config(\n server_name: str, # noqa: ARG001\n server_config_from_value: dict | None,\n server_config_from_db: dict | None,\n) -> dict | None:\n \"\"\"Resolve MCP server config with proper precedence.\n\n Resolves the configuration for an MCP server with the following precedence:\n 1. Database config (takes priority) - ensures edits are reflected\n 2. Config from value/tweaks (fallback) - allows REST API to provide config for new servers\n\n Args:\n server_name: Name of the MCP server\n server_config_from_value: Config provided via value/tweaks (optional)\n server_config_from_db: Config from database (optional)\n\n Returns:\n Final config to use (DB takes priority, falls back to value)\n Returns None if no config found in either location\n \"\"\"\n if server_config_from_db:\n return server_config_from_db\n return server_config_from_value\n\n\nclass MCPToolsComponent(ComponentWithCache):\n schema_inputs: list = []\n tools: list[StructuredTool] = []\n _not_load_actions: bool = False\n _tool_cache: dict = {}\n _last_selected_server: str | None = None # Cache for the last selected server\n\n def __init__(self, **data) -> None:\n super().__init__(**data)\n # Initialize cache keys to avoid CacheMiss when accessing them\n self._ensure_cache_structure()\n\n # Initialize clients with access to the component cache\n self.stdio_client: MCPStdioClient = MCPStdioClient(component_cache=self._shared_component_cache)\n self.streamable_http_client: MCPStreamableHttpClient = MCPStreamableHttpClient(\n component_cache=self._shared_component_cache\n )\n\n def _ensure_cache_structure(self):\n \"\"\"Ensure the cache has the required structure.\"\"\"\n # Check if servers key exists and is not CacheMiss\n servers_value = safe_cache_get(self._shared_component_cache, \"servers\")\n if servers_value is None:\n safe_cache_set(self._shared_component_cache, \"servers\", {})\n\n # Check if last_selected_server key exists and is not CacheMiss\n last_server_value = safe_cache_get(self._shared_component_cache, \"last_selected_server\")\n if last_server_value is None:\n safe_cache_set(self._shared_component_cache, \"last_selected_server\", \"\")\n\n default_keys: list[str] = [\n \"code\",\n \"_type\",\n \"tool_mode\",\n \"tool_placeholder\",\n \"mcp_server\",\n \"tool\",\n \"use_cache\",\n \"verify_ssl\",\n ]\n\n display_name = \"MCP Tools\"\n description = \"Connect to an MCP server to use its tools.\"\n documentation: str = \"https://docs.langflow.org/mcp-tools\"\n icon = \"Mcp\"\n name = \"MCPTools\"\n\n inputs = [\n McpInput(\n name=\"mcp_server\",\n display_name=\"MCP Server\",\n info=\"Select the MCP Server that will be used by this component\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"use_cache\",\n display_name=\"Use Cached Server\",\n info=(\n \"Enable caching of MCP Server and tools to improve performance. \"\n \"Disable to always fetch fresh tools and server updates.\"\n ),\n value=False,\n advanced=True,\n ),\n BoolInput(\n name=\"verify_ssl\",\n display_name=\"Verify SSL Certificate\",\n info=(\n \"Enable SSL certificate verification for HTTPS connections. \"\n \"Disable only for development/testing with self-signed certificates.\"\n ),\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"tool\",\n display_name=\"Tool\",\n options=[],\n value=\"\",\n info=\"Select the tool to execute\",\n show=False,\n required=True,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n info=\"Placeholder for the tool\",\n value=\"\",\n show=False,\n tool_mode=False,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Response\", name=\"response\", method=\"build_output\"),\n ]\n\n async def _validate_schema_inputs(self, tool_obj) -> list[InputTypes]:\n \"\"\"Validate and process schema inputs for a tool.\"\"\"\n try:\n if not tool_obj or not hasattr(tool_obj, \"args_schema\"):\n msg = \"Invalid tool object or missing input schema\"\n raise ValueError(msg)\n\n flat_schema = flatten_schema(tool_obj.args_schema.schema())\n input_schema = create_input_schema_from_json_schema(flat_schema)\n if not input_schema:\n msg = f\"Empty input schema for tool '{tool_obj.name}'\"\n raise ValueError(msg)\n\n schema_inputs = schema_to_langflow_inputs(input_schema)\n if not schema_inputs:\n msg = f\"No input parameters defined for tool '{tool_obj.name}'\"\n await logger.awarning(msg)\n return []\n\n except Exception as e:\n msg = f\"Error validating schema inputs: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n else:\n return schema_inputs\n\n async def update_tool_list(self, mcp_server_value=None):\n # Accepts mcp_server_value as dict {name, config} or uses self.mcp_server\n mcp_server = mcp_server_value if mcp_server_value is not None else getattr(self, \"mcp_server\", None)\n server_name = None\n server_config_from_value = None\n if isinstance(mcp_server, dict):\n server_name = mcp_server.get(\"name\")\n server_config_from_value = mcp_server.get(\"config\")\n else:\n server_name = mcp_server\n if not server_name:\n self.tools = []\n return [], {\"name\": server_name, \"config\": server_config_from_value}\n\n # Check if caching is enabled, default to False\n use_cache = getattr(self, \"use_cache\", False)\n\n # Use shared cache if available and caching is enabled\n cached = None\n if use_cache:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n cached = servers_cache.get(server_name) if isinstance(servers_cache, dict) else None\n\n if cached is not None:\n try:\n self.tools = cached[\"tools\"]\n self.tool_names = cached[\"tool_names\"]\n self._tool_cache = cached[\"tool_cache\"]\n server_config_from_value = cached[\"config\"]\n except (TypeError, KeyError, AttributeError) as e:\n # Handle corrupted cache data by clearing it and continuing to fetch fresh tools\n msg = f\"Unable to use cached data for MCP Server{server_name}: {e}\"\n await logger.awarning(msg)\n # Clear the corrupted cache entry\n current_servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(current_servers_cache, dict) and server_name in current_servers_cache:\n current_servers_cache.pop(server_name)\n safe_cache_set(self._shared_component_cache, \"servers\", current_servers_cache)\n else:\n return self.tools, {\"name\": server_name, \"config\": server_config_from_value}\n\n try:\n # Try to fetch from database first to ensure we have the latest config\n # This ensures database updates (like editing a server) take effect\n try:\n from langflow.api.v2.mcp import get_server\n from langflow.services.database.models.user.crud import get_user_by_id\n except ImportError as e:\n msg = (\n \"Langflow MCP server functionality is not available. \"\n \"This feature requires the full Langflow installation.\"\n )\n raise ImportError(msg) from e\n\n server_config_from_db = None\n async with session_scope() as db:\n if not self.user_id:\n msg = \"User ID is required for fetching MCP tools.\"\n raise ValueError(msg)\n current_user = await get_user_by_id(db, self.user_id)\n\n # Try to get server config from DB/API\n server_config_from_db = await get_server(\n server_name,\n current_user,\n db,\n storage_service=get_storage_service(),\n settings_service=get_settings_service(),\n )\n\n # Resolve config with proper precedence: DB takes priority, falls back to value\n server_config = resolve_mcp_config(\n server_name=server_name,\n server_config_from_value=server_config_from_value,\n server_config_from_db=server_config_from_db,\n )\n\n if not server_config:\n self.tools = []\n return [], {\"name\": server_name, \"config\": server_config}\n\n # Add verify_ssl option to server config if not present\n if \"verify_ssl\" not in server_config:\n verify_ssl = getattr(self, \"verify_ssl\", True)\n server_config[\"verify_ssl\"] = verify_ssl\n\n _, tool_list, tool_cache = await update_tools(\n server_name=server_name,\n server_config=server_config,\n mcp_stdio_client=self.stdio_client,\n mcp_streamable_http_client=self.streamable_http_client,\n )\n\n self.tool_names = [tool.name for tool in tool_list if hasattr(tool, \"name\")]\n self._tool_cache = tool_cache\n self.tools = tool_list\n\n # Cache the result only if caching is enabled\n if use_cache:\n cache_data = {\n \"tools\": tool_list,\n \"tool_names\": self.tool_names,\n \"tool_cache\": tool_cache,\n \"config\": server_config,\n }\n\n # Safely update the servers cache\n current_servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(current_servers_cache, dict):\n current_servers_cache[server_name] = cache_data\n safe_cache_set(self._shared_component_cache, \"servers\", current_servers_cache)\n\n except (TimeoutError, asyncio.TimeoutError) as e:\n msg = f\"Timeout updating tool list: {e!s}\"\n await logger.aexception(msg)\n raise TimeoutError(msg) from e\n except Exception as e:\n msg = f\"Error updating tool list: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n else:\n return tool_list, {\"name\": server_name, \"config\": server_config}\n\n async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Toggle the visibility of connection-specific fields based on the selected mode.\"\"\"\n try:\n if field_name == \"tool\":\n try:\n # Always refresh tools when cache is disabled, or when tools list is empty\n # This ensures database edits are reflected immediately when cache is disabled\n use_cache = getattr(self, \"use_cache\", False)\n if len(self.tools) == 0 or not use_cache:\n try:\n self.tools, build_config[\"mcp_server\"][\"value\"] = await self.update_tool_list()\n build_config[\"tool\"][\"options\"] = [tool.name for tool in self.tools]\n build_config[\"tool\"][\"placeholder\"] = \"Select a tool\"\n except (TimeoutError, asyncio.TimeoutError) as e:\n msg = f\"Timeout updating tool list: {e!s}\"\n await logger.aexception(msg)\n if not build_config[\"tools_metadata\"][\"show\"]:\n build_config[\"tool\"][\"show\"] = True\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"value\"] = \"\"\n build_config[\"tool\"][\"placeholder\"] = \"Timeout on MCP server\"\n else:\n build_config[\"tool\"][\"show\"] = False\n except ValueError:\n if not build_config[\"tools_metadata\"][\"show\"]:\n build_config[\"tool\"][\"show\"] = True\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"value\"] = \"\"\n build_config[\"tool\"][\"placeholder\"] = \"Error on MCP Server\"\n else:\n build_config[\"tool\"][\"show\"] = False\n\n if field_value == \"\":\n return build_config\n tool_obj = None\n for tool in self.tools:\n if tool.name == field_value:\n tool_obj = tool\n break\n if tool_obj is None:\n msg = f\"Tool {field_value} not found in available tools: {self.tools}\"\n await logger.awarning(msg)\n return build_config\n await self._update_tool_config(build_config, field_value)\n except Exception as e:\n build_config[\"tool\"][\"options\"] = []\n msg = f\"Failed to update tools: {e!s}\"\n raise ValueError(msg) from e\n else:\n return build_config\n elif field_name == \"mcp_server\":\n if not field_value:\n build_config[\"tool\"][\"show\"] = False\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"value\"] = \"\"\n build_config[\"tool\"][\"placeholder\"] = \"\"\n build_config[\"tool_placeholder\"][\"tool_mode\"] = False\n self.remove_non_default_keys(build_config)\n return build_config\n\n build_config[\"tool_placeholder\"][\"tool_mode\"] = True\n\n current_server_name = field_value.get(\"name\") if isinstance(field_value, dict) else field_value\n _last_selected_server = safe_cache_get(self._shared_component_cache, \"last_selected_server\", \"\")\n server_changed = current_server_name != _last_selected_server\n\n # Determine if \"Tool Mode\" is active by checking if the tool dropdown is hidden.\n is_in_tool_mode = build_config[\"tools_metadata\"][\"show\"]\n\n # Get use_cache setting to determine if we should use cached data\n use_cache = getattr(self, \"use_cache\", False)\n\n # Fast path: if server didn't change and we already have options, keep them as-is\n # BUT only if caching is enabled or we're in tool mode\n existing_options = build_config.get(\"tool\", {}).get(\"options\") or []\n if not server_changed and existing_options:\n # In non-tool mode with cache disabled, skip the fast path to force refresh\n if not is_in_tool_mode and not use_cache:\n pass # Continue to refresh logic below\n else:\n if not is_in_tool_mode:\n build_config[\"tool\"][\"show\"] = True\n return build_config\n\n # To avoid unnecessary updates, only proceed if the server has actually changed\n # OR if caching is disabled (to force refresh in non-tool mode)\n if (_last_selected_server in (current_server_name, \"\")) and build_config[\"tool\"][\"show\"] and use_cache:\n if current_server_name:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(servers_cache, dict):\n cached = servers_cache.get(current_server_name)\n if cached is not None and cached.get(\"tool_names\"):\n cached_tools = cached[\"tool_names\"]\n current_tools = build_config[\"tool\"][\"options\"]\n if current_tools == cached_tools:\n return build_config\n else:\n return build_config\n safe_cache_set(self._shared_component_cache, \"last_selected_server\", current_server_name)\n\n # When cache is disabled, clear any cached data for this server\n # This ensures we always fetch fresh data from the database\n if not use_cache and current_server_name:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(servers_cache, dict) and current_server_name in servers_cache:\n servers_cache.pop(current_server_name)\n safe_cache_set(self._shared_component_cache, \"servers\", servers_cache)\n\n # Check if tools are already cached for this server before clearing\n cached_tools = None\n if current_server_name and use_cache:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(servers_cache, dict):\n cached = servers_cache.get(current_server_name)\n if cached is not None:\n try:\n cached_tools = cached[\"tools\"]\n self.tools = cached_tools\n self.tool_names = cached[\"tool_names\"]\n self._tool_cache = cached[\"tool_cache\"]\n except (TypeError, KeyError, AttributeError) as e:\n # Handle corrupted cache data by ignoring it\n msg = f\"Unable to use cached data for MCP Server,{current_server_name}: {e}\"\n await logger.awarning(msg)\n cached_tools = None\n\n # Clear tools when cache is disabled OR when we don't have cached tools\n # This ensures fresh tools are fetched after database edits\n if not cached_tools or not use_cache:\n self.tools = [] # Clear previous tools to force refresh\n\n # Clear previous tool inputs if:\n # 1. Server actually changed\n # 2. Cache is disabled (meaning tool list will be refreshed)\n if server_changed or not use_cache:\n self.remove_non_default_keys(build_config)\n\n # Only show the tool dropdown if not in tool_mode\n if not is_in_tool_mode:\n build_config[\"tool\"][\"show\"] = True\n if cached_tools:\n # Use cached tools to populate options immediately\n build_config[\"tool\"][\"options\"] = [tool.name for tool in cached_tools]\n build_config[\"tool\"][\"placeholder\"] = \"Select a tool\"\n else:\n # Show loading state only when we need to fetch tools\n build_config[\"tool\"][\"placeholder\"] = \"Loading tools...\"\n build_config[\"tool\"][\"options\"] = []\n # Force a value refresh when:\n # 1. Server changed\n # 2. We don't have cached tools\n # 3. Cache is disabled (to force refresh on config changes)\n if server_changed or not cached_tools or not use_cache:\n build_config[\"tool\"][\"value\"] = uuid.uuid4()\n else:\n # Keep the tool dropdown hidden if in tool_mode\n self._not_load_actions = True\n build_config[\"tool\"][\"show\"] = False\n\n elif field_name == \"tool_mode\":\n build_config[\"tool\"][\"placeholder\"] = \"\"\n build_config[\"tool\"][\"show\"] = not bool(field_value) and bool(build_config[\"mcp_server\"])\n self.remove_non_default_keys(build_config)\n self.tool = build_config[\"tool\"][\"value\"]\n if field_value:\n self._not_load_actions = True\n else:\n build_config[\"tool\"][\"value\"] = uuid.uuid4()\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"show\"] = True\n build_config[\"tool\"][\"placeholder\"] = \"Loading tools...\"\n elif field_name == \"tools_metadata\":\n self._not_load_actions = False\n\n except Exception as e:\n msg = f\"Error in update_build_config: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n else:\n return build_config\n\n def get_inputs_for_all_tools(self, tools: list) -> dict:\n \"\"\"Get input schemas for all tools.\"\"\"\n inputs = {}\n for tool in tools:\n if not tool or not hasattr(tool, \"name\"):\n continue\n try:\n flat_schema = flatten_schema(tool.args_schema.schema())\n input_schema = create_input_schema_from_json_schema(flat_schema)\n langflow_inputs = schema_to_langflow_inputs(input_schema)\n inputs[tool.name] = langflow_inputs\n except (AttributeError, ValueError, TypeError, KeyError) as e:\n msg = f\"Error getting inputs for tool {getattr(tool, 'name', 'unknown')}: {e!s}\"\n logger.exception(msg)\n continue\n return inputs\n\n def remove_non_default_keys(self, build_config: dict) -> None:\n \"\"\"Remove non-default keys from the build config.\"\"\"\n for key in list(build_config.keys()):\n if key not in self.default_keys:\n build_config.pop(key)\n\n async def _update_tool_config(self, build_config: dict, tool_name: str) -> None:\n \"\"\"Update tool configuration with proper error handling.\"\"\"\n if not self.tools:\n self.tools, build_config[\"mcp_server\"][\"value\"] = await self.update_tool_list()\n\n if not tool_name:\n return\n\n tool_obj = next((tool for tool in self.tools if tool.name == tool_name), None)\n if not tool_obj:\n msg = f\"Tool {tool_name} not found in available tools: {self.tools}\"\n self.remove_non_default_keys(build_config)\n build_config[\"tool\"][\"value\"] = \"\"\n await logger.awarning(msg)\n return\n\n try:\n # Store current values before removing inputs (only for the current tool)\n current_values = {}\n for key, value in build_config.items():\n if key not in self.default_keys and isinstance(value, dict) and \"value\" in value:\n current_values[key] = value[\"value\"]\n\n # Remove ALL non-default keys (all previous tool inputs)\n self.remove_non_default_keys(build_config)\n\n # Get and validate new inputs for the selected tool\n self.schema_inputs = await self._validate_schema_inputs(tool_obj)\n if not self.schema_inputs:\n msg = f\"No input parameters to configure for tool '{tool_name}'\"\n await logger.ainfo(msg)\n return\n\n # Add new inputs to build config for the selected tool only\n for schema_input in self.schema_inputs:\n if not schema_input or not hasattr(schema_input, \"name\"):\n msg = \"Invalid schema input detected, skipping\"\n await logger.awarning(msg)\n continue\n\n try:\n name = schema_input.name\n input_dict = schema_input.to_dict()\n input_dict.setdefault(\"value\", None)\n input_dict.setdefault(\"required\", True)\n\n build_config[name] = input_dict\n\n # Preserve existing value if the parameter name exists in current_values\n if name in current_values:\n build_config[name][\"value\"] = current_values[name]\n\n except (AttributeError, KeyError, TypeError) as e:\n msg = f\"Error processing schema input {schema_input}: {e!s}\"\n await logger.aexception(msg)\n continue\n except ValueError as e:\n msg = f\"Schema validation error for tool {tool_name}: {e!s}\"\n await logger.aexception(msg)\n self.schema_inputs = []\n return\n except (AttributeError, KeyError, TypeError) as e:\n msg = f\"Error updating tool config: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n\n async def build_output(self) -> DataFrame:\n \"\"\"Build output with improved error handling and validation.\"\"\"\n try:\n self.tools, _ = await self.update_tool_list()\n if self.tool != \"\":\n # Set session context for persistent MCP sessions using Langflow session ID\n session_context = self._get_session_context()\n if session_context:\n self.stdio_client.set_session_context(session_context)\n self.streamable_http_client.set_session_context(session_context)\n exec_tool = self._tool_cache[self.tool]\n tool_args = self.get_inputs_for_all_tools(self.tools)[self.tool]\n kwargs = {}\n for arg in tool_args:\n value = getattr(self, arg.name, None)\n if value is not None:\n if isinstance(value, Message):\n kwargs[arg.name] = value.text\n else:\n kwargs[arg.name] = value\n\n unflattened_kwargs = maybe_unflatten_dict(kwargs)\n\n output = await exec_tool.coroutine(**unflattened_kwargs)\n tool_content = []\n for item in output.content:\n item_dict = item.model_dump()\n item_dict = self.process_output_item(item_dict)\n tool_content.append(item_dict)\n\n if isinstance(tool_content, list) and all(isinstance(x, dict) for x in tool_content):\n return DataFrame(tool_content)\n return DataFrame(data=tool_content)\n return DataFrame(data=[{\"error\": \"You must select a tool\"}])\n except Exception as e:\n msg = f\"Error in build_output: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n\n def process_output_item(self, item_dict):\n \"\"\"Process the output of a tool.\"\"\"\n if item_dict.get(\"type\") == \"text\":\n text = item_dict.get(\"text\")\n try:\n return json.loads(text)\n except json.JSONDecodeError:\n return item_dict\n return item_dict\n\n def _get_session_context(self) -> str | None:\n \"\"\"Get the Langflow session ID for MCP session caching.\"\"\"\n # Try to get session ID from the component's execution context\n if hasattr(self, \"graph\") and hasattr(self.graph, \"session_id\"):\n session_id = self.graph.session_id\n # Include server name to ensure different servers get different sessions\n server_name = \"\"\n mcp_server = getattr(self, \"mcp_server\", None)\n if isinstance(mcp_server, dict):\n server_name = mcp_server.get(\"name\", \"\")\n elif mcp_server:\n server_name = str(mcp_server)\n return f\"{session_id}_{server_name}\" if session_id else None\n return None\n\n async def _get_tools(self):\n \"\"\"Get cached tools or update if necessary.\"\"\"\n mcp_server = getattr(self, \"mcp_server\", None)\n if not self._not_load_actions:\n tools, _ = await self.update_tool_list(mcp_server)\n return tools\n return []\n" + "value": "from __future__ import annotations\n\nimport asyncio\nimport json\nimport uuid\n\nfrom langchain_core.tools import StructuredTool # noqa: TC002\n\nfrom lfx.base.agents.utils import maybe_unflatten_dict, safe_cache_get, safe_cache_set\nfrom lfx.base.mcp.util import (\n MCPStdioClient,\n MCPStreamableHttpClient,\n create_input_schema_from_json_schema,\n update_tools,\n)\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.inputs.inputs import InputTypes # noqa: TC001\nfrom lfx.io import BoolInput, DropdownInput, McpInput, MessageTextInput, Output\nfrom lfx.io.schema import flatten_schema, schema_to_langflow_inputs\nfrom lfx.log.logger import logger\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.services.deps import get_settings_service, get_storage_service, session_scope\n\n\ndef resolve_mcp_config(\n server_name: str, # noqa: ARG001\n server_config_from_value: dict | None,\n server_config_from_db: dict | None,\n) -> dict | None:\n \"\"\"Resolve MCP server config with proper precedence.\n\n Resolves the configuration for an MCP server with the following precedence:\n 1. Database config (takes priority) - ensures edits are reflected\n 2. Config from value/tweaks (fallback) - allows REST API to provide config for new servers\n\n Args:\n server_name: Name of the MCP server\n server_config_from_value: Config provided via value/tweaks (optional)\n server_config_from_db: Config from database (optional)\n\n Returns:\n Final config to use (DB takes priority, falls back to value)\n Returns None if no config found in either location\n \"\"\"\n if server_config_from_db:\n return server_config_from_db\n return server_config_from_value\n\n\nclass MCPToolsComponent(ComponentWithCache):\n schema_inputs: list = []\n tools: list[StructuredTool] = []\n _not_load_actions: bool = False\n _tool_cache: dict = {}\n _last_selected_server: str | None = None # Cache for the last selected server\n\n def __init__(self, **data) -> None:\n super().__init__(**data)\n # Initialize cache keys to avoid CacheMiss when accessing them\n self._ensure_cache_structure()\n\n # Initialize clients with access to the component cache\n self.stdio_client: MCPStdioClient = MCPStdioClient(component_cache=self._shared_component_cache)\n self.streamable_http_client: MCPStreamableHttpClient = MCPStreamableHttpClient(\n component_cache=self._shared_component_cache\n )\n\n def _ensure_cache_structure(self):\n \"\"\"Ensure the cache has the required structure.\"\"\"\n # Check if servers key exists and is not CacheMiss\n servers_value = safe_cache_get(self._shared_component_cache, \"servers\")\n if servers_value is None:\n safe_cache_set(self._shared_component_cache, \"servers\", {})\n\n # Check if last_selected_server key exists and is not CacheMiss\n last_server_value = safe_cache_get(self._shared_component_cache, \"last_selected_server\")\n if last_server_value is None:\n safe_cache_set(self._shared_component_cache, \"last_selected_server\", \"\")\n\n default_keys: list[str] = [\n \"code\",\n \"_type\",\n \"tool_mode\",\n \"tool_placeholder\",\n \"mcp_server\",\n \"tool\",\n \"use_cache\",\n \"verify_ssl\",\n ]\n\n display_name = \"MCP Tools\"\n description = \"Connect to an MCP server to use its tools.\"\n documentation: str = \"https://docs.langflow.org/mcp-tools\"\n icon = \"Mcp\"\n name = \"MCPTools\"\n\n inputs = [\n McpInput(\n name=\"mcp_server\",\n display_name=\"MCP Server\",\n info=\"Select the MCP Server that will be used by this component\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"use_cache\",\n display_name=\"Use Cached Server\",\n info=(\n \"Enable caching of MCP Server and tools to improve performance. \"\n \"Disable to always fetch fresh tools and server updates.\"\n ),\n value=False,\n advanced=True,\n ),\n BoolInput(\n name=\"verify_ssl\",\n display_name=\"Verify SSL Certificate\",\n info=(\n \"Enable SSL certificate verification for HTTPS connections. \"\n \"Disable only for development/testing with self-signed certificates.\"\n ),\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"tool\",\n display_name=\"Tool\",\n options=[],\n value=\"\",\n info=\"Select the tool to execute\",\n show=False,\n required=True,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n info=\"Placeholder for the tool\",\n value=\"\",\n show=False,\n tool_mode=False,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Response\", name=\"response\", method=\"build_output\"),\n ]\n\n async def _validate_schema_inputs(self, tool_obj) -> list[InputTypes]:\n \"\"\"Validate and process schema inputs for a tool.\"\"\"\n try:\n if not tool_obj or not hasattr(tool_obj, \"args_schema\"):\n msg = \"Invalid tool object or missing input schema\"\n raise ValueError(msg)\n\n flat_schema = flatten_schema(tool_obj.args_schema.schema())\n input_schema = create_input_schema_from_json_schema(flat_schema)\n if not input_schema:\n msg = f\"Empty input schema for tool '{tool_obj.name}'\"\n raise ValueError(msg)\n\n schema_inputs = schema_to_langflow_inputs(input_schema)\n if not schema_inputs:\n msg = f\"No input parameters defined for tool '{tool_obj.name}'\"\n await logger.awarning(msg)\n return []\n\n except Exception as e:\n msg = f\"Error validating schema inputs: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n else:\n return schema_inputs\n\n async def update_tool_list(self, mcp_server_value=None):\n # Accepts mcp_server_value as dict {name, config} or uses self.mcp_server\n mcp_server = mcp_server_value if mcp_server_value is not None else getattr(self, \"mcp_server\", None)\n server_name = None\n server_config_from_value = None\n if isinstance(mcp_server, dict):\n server_name = mcp_server.get(\"name\")\n server_config_from_value = mcp_server.get(\"config\")\n else:\n server_name = mcp_server\n if not server_name:\n self.tools = []\n return [], {\"name\": server_name, \"config\": server_config_from_value}\n\n # Check if caching is enabled, default to False\n use_cache = getattr(self, \"use_cache\", False)\n\n # Use shared cache if available and caching is enabled\n cached = None\n if use_cache:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n cached = servers_cache.get(server_name) if isinstance(servers_cache, dict) else None\n\n if cached is not None:\n try:\n self.tools = cached[\"tools\"]\n self.tool_names = cached[\"tool_names\"]\n self._tool_cache = cached[\"tool_cache\"]\n server_config_from_value = cached[\"config\"]\n except (TypeError, KeyError, AttributeError) as e:\n # Handle corrupted cache data by clearing it and continuing to fetch fresh tools\n msg = f\"Unable to use cached data for MCP Server{server_name}: {e}\"\n await logger.awarning(msg)\n # Clear the corrupted cache entry\n current_servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(current_servers_cache, dict) and server_name in current_servers_cache:\n current_servers_cache.pop(server_name)\n safe_cache_set(self._shared_component_cache, \"servers\", current_servers_cache)\n else:\n return self.tools, {\"name\": server_name, \"config\": server_config_from_value}\n\n try:\n # Try to fetch from database first to ensure we have the latest config\n # This ensures database updates (like editing a server) take effect\n try:\n from langflow.api.v2.mcp import get_server\n from langflow.services.database.models.user.crud import get_user_by_id\n except ImportError as e:\n msg = (\n \"Langflow MCP server functionality is not available. \"\n \"This feature requires the full Langflow installation.\"\n )\n raise ImportError(msg) from e\n\n server_config_from_db = None\n async with session_scope() as db:\n if not self.user_id:\n msg = \"User ID is required for fetching MCP tools.\"\n raise ValueError(msg)\n current_user = await get_user_by_id(db, self.user_id)\n\n # Try to get server config from DB/API\n server_config_from_db = await get_server(\n server_name,\n current_user,\n db,\n storage_service=get_storage_service(),\n settings_service=get_settings_service(),\n )\n\n # Resolve config with proper precedence: DB takes priority, falls back to value\n server_config = resolve_mcp_config(\n server_name=server_name,\n server_config_from_value=server_config_from_value,\n server_config_from_db=server_config_from_db,\n )\n\n if not server_config:\n self.tools = []\n return [], {\"name\": server_name, \"config\": server_config}\n\n # Add verify_ssl option to server config if not present\n if \"verify_ssl\" not in server_config:\n verify_ssl = getattr(self, \"verify_ssl\", True)\n server_config[\"verify_ssl\"] = verify_ssl\n\n _, tool_list, tool_cache = await update_tools(\n server_name=server_name,\n server_config=server_config,\n mcp_stdio_client=self.stdio_client,\n mcp_streamable_http_client=self.streamable_http_client,\n )\n\n self.tool_names = [tool.name for tool in tool_list if hasattr(tool, \"name\")]\n self._tool_cache = tool_cache\n self.tools = tool_list\n\n # Cache the result only if caching is enabled\n if use_cache:\n cache_data = {\n \"tools\": tool_list,\n \"tool_names\": self.tool_names,\n \"tool_cache\": tool_cache,\n \"config\": server_config,\n }\n\n # Safely update the servers cache\n current_servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(current_servers_cache, dict):\n current_servers_cache[server_name] = cache_data\n safe_cache_set(self._shared_component_cache, \"servers\", current_servers_cache)\n\n except (TimeoutError, asyncio.TimeoutError) as e:\n msg = f\"Timeout updating tool list: {e!s}\"\n await logger.aexception(msg)\n raise TimeoutError(msg) from e\n except Exception as e:\n msg = f\"Error updating tool list: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n else:\n return tool_list, {\"name\": server_name, \"config\": server_config}\n\n async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Toggle the visibility of connection-specific fields based on the selected mode.\"\"\"\n try:\n if field_name == \"tool\":\n try:\n # Always refresh tools when cache is disabled, or when tools list is empty\n # This ensures database edits are reflected immediately when cache is disabled\n use_cache = getattr(self, \"use_cache\", False)\n if len(self.tools) == 0 or not use_cache:\n try:\n self.tools, build_config[\"mcp_server\"][\"value\"] = await self.update_tool_list()\n build_config[\"tool\"][\"options\"] = [tool.name for tool in self.tools]\n build_config[\"tool\"][\"placeholder\"] = \"Select a tool\"\n except (TimeoutError, asyncio.TimeoutError) as e:\n msg = f\"Timeout updating tool list: {e!s}\"\n await logger.aexception(msg)\n if not build_config[\"tools_metadata\"][\"show\"]:\n build_config[\"tool\"][\"show\"] = True\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"value\"] = \"\"\n build_config[\"tool\"][\"placeholder\"] = \"Timeout on MCP server\"\n else:\n build_config[\"tool\"][\"show\"] = False\n except ValueError:\n if not build_config[\"tools_metadata\"][\"show\"]:\n build_config[\"tool\"][\"show\"] = True\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"value\"] = \"\"\n build_config[\"tool\"][\"placeholder\"] = \"Error on MCP Server\"\n else:\n build_config[\"tool\"][\"show\"] = False\n\n if field_value == \"\":\n return build_config\n tool_obj = None\n for tool in self.tools:\n if tool.name == field_value:\n tool_obj = tool\n break\n if tool_obj is None:\n msg = f\"Tool {field_value} not found in available tools: {self.tools}\"\n await logger.awarning(msg)\n return build_config\n await self._update_tool_config(build_config, field_value)\n except Exception as e:\n build_config[\"tool\"][\"options\"] = []\n msg = f\"Failed to update tools: {e!s}\"\n raise ValueError(msg) from e\n else:\n return build_config\n elif field_name == \"mcp_server\":\n if not field_value:\n build_config[\"tool\"][\"show\"] = False\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"value\"] = \"\"\n build_config[\"tool\"][\"placeholder\"] = \"\"\n build_config[\"tool_placeholder\"][\"tool_mode\"] = False\n self.remove_non_default_keys(build_config)\n return build_config\n\n build_config[\"tool_placeholder\"][\"tool_mode\"] = True\n\n current_server_name = field_value.get(\"name\") if isinstance(field_value, dict) else field_value\n _last_selected_server = safe_cache_get(self._shared_component_cache, \"last_selected_server\", \"\")\n server_changed = current_server_name != _last_selected_server\n\n # Determine if \"Tool Mode\" is active by checking if the tool dropdown is hidden.\n is_in_tool_mode = build_config[\"tools_metadata\"][\"show\"]\n\n # Get use_cache setting to determine if we should use cached data\n use_cache = getattr(self, \"use_cache\", False)\n\n # Fast path: if server didn't change and we already have options, keep them as-is\n # BUT only if caching is enabled or we're in tool mode\n existing_options = build_config.get(\"tool\", {}).get(\"options\") or []\n if not server_changed and existing_options:\n # In non-tool mode with cache disabled, skip the fast path to force refresh\n if not is_in_tool_mode and not use_cache:\n pass # Continue to refresh logic below\n else:\n if not is_in_tool_mode:\n build_config[\"tool\"][\"show\"] = True\n return build_config\n\n # To avoid unnecessary updates, only proceed if the server has actually changed\n # OR if caching is disabled (to force refresh in non-tool mode)\n if (_last_selected_server in (current_server_name, \"\")) and build_config[\"tool\"][\"show\"] and use_cache:\n if current_server_name:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(servers_cache, dict):\n cached = servers_cache.get(current_server_name)\n if cached is not None and cached.get(\"tool_names\"):\n cached_tools = cached[\"tool_names\"]\n current_tools = build_config[\"tool\"][\"options\"]\n if current_tools == cached_tools:\n return build_config\n else:\n return build_config\n safe_cache_set(self._shared_component_cache, \"last_selected_server\", current_server_name)\n\n # When cache is disabled, clear any cached data for this server\n # This ensures we always fetch fresh data from the database\n if not use_cache and current_server_name:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(servers_cache, dict) and current_server_name in servers_cache:\n servers_cache.pop(current_server_name)\n safe_cache_set(self._shared_component_cache, \"servers\", servers_cache)\n\n # Check if tools are already cached for this server before clearing\n cached_tools = None\n if current_server_name and use_cache:\n servers_cache = safe_cache_get(self._shared_component_cache, \"servers\", {})\n if isinstance(servers_cache, dict):\n cached = servers_cache.get(current_server_name)\n if cached is not None:\n try:\n cached_tools = cached[\"tools\"]\n self.tools = cached_tools\n self.tool_names = cached[\"tool_names\"]\n self._tool_cache = cached[\"tool_cache\"]\n except (TypeError, KeyError, AttributeError) as e:\n # Handle corrupted cache data by ignoring it\n msg = f\"Unable to use cached data for MCP Server,{current_server_name}: {e}\"\n await logger.awarning(msg)\n cached_tools = None\n\n # Clear tools when cache is disabled OR when we don't have cached tools\n # This ensures fresh tools are fetched after database edits\n if not cached_tools or not use_cache:\n self.tools = [] # Clear previous tools to force refresh\n\n # Clear previous tool inputs if:\n # 1. Server actually changed\n # 2. Cache is disabled (meaning tool list will be refreshed)\n if server_changed or not use_cache:\n self.remove_non_default_keys(build_config)\n\n # Only show the tool dropdown if not in tool_mode\n if not is_in_tool_mode:\n build_config[\"tool\"][\"show\"] = True\n if cached_tools:\n # Use cached tools to populate options immediately\n build_config[\"tool\"][\"options\"] = [tool.name for tool in cached_tools]\n build_config[\"tool\"][\"placeholder\"] = \"Select a tool\"\n else:\n # Show loading state only when we need to fetch tools\n build_config[\"tool\"][\"placeholder\"] = \"Loading tools...\"\n build_config[\"tool\"][\"options\"] = []\n # Force a value refresh when:\n # 1. Server changed\n # 2. We don't have cached tools\n # 3. Cache is disabled (to force refresh on config changes)\n if server_changed or not cached_tools or not use_cache:\n build_config[\"tool\"][\"value\"] = uuid.uuid4()\n else:\n # Keep the tool dropdown hidden if in tool_mode\n self._not_load_actions = True\n build_config[\"tool\"][\"show\"] = False\n\n elif field_name == \"tool_mode\":\n build_config[\"tool\"][\"placeholder\"] = \"\"\n build_config[\"tool\"][\"show\"] = not bool(field_value) and bool(build_config[\"mcp_server\"])\n self.remove_non_default_keys(build_config)\n self.tool = build_config[\"tool\"][\"value\"]\n if field_value:\n self._not_load_actions = True\n else:\n build_config[\"tool\"][\"value\"] = uuid.uuid4()\n build_config[\"tool\"][\"options\"] = []\n build_config[\"tool\"][\"show\"] = True\n build_config[\"tool\"][\"placeholder\"] = \"Loading tools...\"\n elif field_name == \"tools_metadata\":\n self._not_load_actions = False\n\n except Exception as e:\n msg = f\"Error in update_build_config: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n else:\n return build_config\n\n def get_inputs_for_all_tools(self, tools: list) -> dict:\n \"\"\"Get input schemas for all tools.\"\"\"\n inputs = {}\n for tool in tools:\n if not tool or not hasattr(tool, \"name\"):\n continue\n try:\n flat_schema = flatten_schema(tool.args_schema.schema())\n input_schema = create_input_schema_from_json_schema(flat_schema)\n langflow_inputs = schema_to_langflow_inputs(input_schema)\n inputs[tool.name] = langflow_inputs\n except (AttributeError, ValueError, TypeError, KeyError) as e:\n msg = f\"Error getting inputs for tool {getattr(tool, 'name', 'unknown')}: {e!s}\"\n logger.exception(msg)\n continue\n return inputs\n\n def remove_non_default_keys(self, build_config: dict) -> None:\n \"\"\"Remove non-default keys from the build config.\"\"\"\n for key in list(build_config.keys()):\n if key not in self.default_keys:\n build_config.pop(key)\n\n async def _update_tool_config(self, build_config: dict, tool_name: str) -> None:\n \"\"\"Update tool configuration with proper error handling.\"\"\"\n if not self.tools:\n self.tools, build_config[\"mcp_server\"][\"value\"] = await self.update_tool_list()\n\n if not tool_name:\n return\n\n tool_obj = next((tool for tool in self.tools if tool.name == tool_name), None)\n if not tool_obj:\n msg = f\"Tool {tool_name} not found in available tools: {self.tools}\"\n self.remove_non_default_keys(build_config)\n build_config[\"tool\"][\"value\"] = \"\"\n await logger.awarning(msg)\n return\n\n try:\n # Store current values before removing inputs (only for the current tool)\n current_values = {}\n for key, value in build_config.items():\n if key not in self.default_keys and isinstance(value, dict) and \"value\" in value:\n current_values[key] = value[\"value\"]\n\n # Remove ALL non-default keys (all previous tool inputs)\n self.remove_non_default_keys(build_config)\n\n # Get and validate new inputs for the selected tool\n self.schema_inputs = await self._validate_schema_inputs(tool_obj)\n if not self.schema_inputs:\n msg = f\"No input parameters to configure for tool '{tool_name}'\"\n await logger.ainfo(msg)\n return\n\n # Add new inputs to build config for the selected tool only\n for schema_input in self.schema_inputs:\n if not schema_input or not hasattr(schema_input, \"name\"):\n msg = \"Invalid schema input detected, skipping\"\n await logger.awarning(msg)\n continue\n\n try:\n name = schema_input.name\n input_dict = schema_input.to_dict()\n input_dict.setdefault(\"value\", None)\n input_dict.setdefault(\"required\", True)\n\n build_config[name] = input_dict\n\n # Preserve existing value if the parameter name exists in current_values\n if name in current_values:\n build_config[name][\"value\"] = current_values[name]\n\n except (AttributeError, KeyError, TypeError) as e:\n msg = f\"Error processing schema input {schema_input}: {e!s}\"\n await logger.aexception(msg)\n continue\n except ValueError as e:\n msg = f\"Schema validation error for tool {tool_name}: {e!s}\"\n await logger.aexception(msg)\n self.schema_inputs = []\n return\n except (AttributeError, KeyError, TypeError) as e:\n msg = f\"Error updating tool config: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n\n async def build_output(self) -> DataFrame:\n \"\"\"Build output with improved error handling and validation.\"\"\"\n try:\n self.tools, _ = await self.update_tool_list()\n if self.tool != \"\":\n # Set session context for persistent MCP sessions using Langflow session ID\n session_context = self._get_session_context()\n if session_context:\n self.stdio_client.set_session_context(session_context)\n self.streamable_http_client.set_session_context(session_context)\n exec_tool = self._tool_cache[self.tool]\n tool_args = self.get_inputs_for_all_tools(self.tools)[self.tool]\n kwargs = {}\n for arg in tool_args:\n value = getattr(self, arg.name, None)\n if value is not None:\n if isinstance(value, Message):\n kwargs[arg.name] = value.text\n else:\n kwargs[arg.name] = value\n\n unflattened_kwargs = maybe_unflatten_dict(kwargs)\n\n output = await exec_tool.coroutine(**unflattened_kwargs)\n tool_content = []\n for item in output.content:\n item_dict = item.model_dump()\n item_dict = self.process_output_item(item_dict)\n tool_content.append(item_dict)\n\n if isinstance(tool_content, list) and all(isinstance(x, dict) for x in tool_content):\n return DataFrame(tool_content)\n return DataFrame(data=tool_content)\n return DataFrame(data=[{\"error\": \"You must select a tool\"}])\n except Exception as e:\n msg = f\"Error in build_output: {e!s}\"\n await logger.aexception(msg)\n raise ValueError(msg) from e\n\n def process_output_item(self, item_dict):\n \"\"\"Process the output of a tool.\"\"\"\n if item_dict.get(\"type\") == \"text\":\n text = item_dict.get(\"text\")\n try:\n parsed = json.loads(text)\n # Ensure we always return a dictionary for DataFrame compatibility\n if isinstance(parsed, dict):\n return parsed\n # Wrap non-dict parsed values in a dictionary\n return {\"text\": text, \"parsed_value\": parsed, \"type\": \"text\"} # noqa: TRY300\n except json.JSONDecodeError:\n return item_dict\n return item_dict\n\n def _get_session_context(self) -> str | None:\n \"\"\"Get the Langflow session ID for MCP session caching.\"\"\"\n # Try to get session ID from the component's execution context\n if hasattr(self, \"graph\") and hasattr(self.graph, \"session_id\"):\n session_id = self.graph.session_id\n # Include server name to ensure different servers get different sessions\n server_name = \"\"\n mcp_server = getattr(self, \"mcp_server\", None)\n if isinstance(mcp_server, dict):\n server_name = mcp_server.get(\"name\", \"\")\n elif mcp_server:\n server_name = str(mcp_server)\n return f\"{session_id}_{server_name}\" if session_id else None\n return None\n\n async def _get_tools(self):\n \"\"\"Get cached tools or update if necessary.\"\"\"\n mcp_server = getattr(self, \"mcp_server\", None)\n if not self._not_load_actions:\n tools, _ = await self.update_tool_list(mcp_server)\n return tools\n return []\n" }, "mcp_server": { "_input_type": "McpInput", diff --git "a/src/backend/base/langflow/initial_setup/starter_projects/Pok\303\251dex Agent.json" "b/src/backend/base/langflow/initial_setup/starter_projects/Pok\303\251dex Agent.json" index 52c3d322bbd2..d8fa440d1634 100644 --- "a/src/backend/base/langflow/initial_setup/starter_projects/Pok\303\251dex Agent.json" +++ "b/src/backend/base/langflow/initial_setup/starter_projects/Pok\303\251dex Agent.json" @@ -392,7 +392,7 @@ "legacy": false, "lf_version": "1.2.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -468,7 +468,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -765,7 +765,7 @@ "key": "APIRequest", "legacy": false, "metadata": { - "code_hash": "04d62aab3a77", + "code_hash": "7f013aba27c9", "dependencies": { "dependencies": [ { @@ -882,7 +882,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\nimport tempfile\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Any\nfrom urllib.parse import parse_qsl, urlencode, urlparse, urlunparse\n\nimport aiofiles\nimport aiofiles.os as aiofiles_os\nimport httpx\nimport validators\n\nfrom lfx.base.curl.parse import parse_context\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import TabInput\nfrom lfx.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n IntInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.component_utils import set_current_fields, set_field_advanced, set_field_display\nfrom lfx.utils.ssrf_protection import SSRFProtectionError, validate_url_for_ssrf\n\n# Define fields for each mode\nMODE_FIELDS = {\n \"URL\": [\n \"url_input\",\n \"method\",\n ],\n \"cURL\": [\"curl_input\"],\n}\n\n# Fields that should always be visible\nDEFAULT_FIELDS = [\"mode\"]\n\n\nclass APIRequestComponent(Component):\n display_name = \"API Request\"\n description = \"Make HTTP requests using URL or cURL commands.\"\n documentation: str = \"https://docs.langflow.org/api-request\"\n icon = \"Globe\"\n name = \"APIRequest\"\n\n inputs = [\n MessageTextInput(\n name=\"url_input\",\n display_name=\"URL\",\n info=\"Enter the URL for the request.\",\n advanced=False,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"curl_input\",\n display_name=\"cURL\",\n info=(\n \"Paste a curl command to populate the fields. \"\n \"This will fill in the dictionary fields for headers and body.\"\n ),\n real_time_refresh=True,\n tool_mode=True,\n advanced=True,\n show=False,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Method\",\n options=[\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"],\n value=\"GET\",\n info=\"The HTTP method to use.\",\n real_time_refresh=True,\n ),\n TabInput(\n name=\"mode\",\n display_name=\"Mode\",\n options=[\"URL\", \"cURL\"],\n value=\"URL\",\n info=\"Enable cURL mode to populate fields from a cURL command.\",\n real_time_refresh=True,\n ),\n DataInput(\n name=\"query_params\",\n display_name=\"Query Parameters\",\n info=\"The query parameters to append to the URL.\",\n advanced=True,\n ),\n TableInput(\n name=\"body\",\n display_name=\"Body\",\n info=\"The body to send with the request as a dictionary (for POST, PATCH, PUT).\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Parameter name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"description\": \"Parameter value\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n advanced=True,\n real_time_refresh=True,\n ),\n TableInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"The headers to send with the request\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Header\",\n \"type\": \"str\",\n \"description\": \"Header name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Header value\",\n },\n ],\n value=[{\"key\": \"User-Agent\", \"value\": \"Langflow/1.0\"}],\n advanced=True,\n input_types=[\"Data\"],\n real_time_refresh=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n value=30,\n info=\"The timeout to use for the request.\",\n advanced=True,\n ),\n BoolInput(\n name=\"follow_redirects\",\n display_name=\"Follow Redirects\",\n value=False,\n info=(\n \"Whether to follow HTTP redirects. \"\n \"WARNING: Enabling redirects may allow SSRF bypass attacks where a public URL \"\n \"redirects to internal resources. Only enable if you trust the target server. \"\n \"See OWASP SSRF Prevention Cheat Sheet for details.\"\n ),\n advanced=True,\n ),\n BoolInput(\n name=\"save_to_file\",\n display_name=\"Save to File\",\n value=False,\n info=\"Save the API response to a temporary file\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_httpx_metadata\",\n display_name=\"Include HTTPx Metadata\",\n value=False,\n info=(\n \"Include properties such as headers, status_code, response_headers, \"\n \"and redirection_history in the output.\"\n ),\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"API Response\", name=\"data\", method=\"make_api_request\"),\n ]\n\n def _parse_json_value(self, value: Any) -> Any:\n \"\"\"Parse a value that might be a JSON string.\"\"\"\n if not isinstance(value, str):\n return value\n\n try:\n parsed = json.loads(value)\n except json.JSONDecodeError:\n return value\n else:\n return parsed\n\n def _process_body(self, body: Any) -> dict:\n \"\"\"Process the body input into a valid dictionary.\"\"\"\n if body is None:\n return {}\n if hasattr(body, \"data\"):\n body = body.data\n if isinstance(body, dict):\n return self._process_dict_body(body)\n if isinstance(body, str):\n return self._process_string_body(body)\n if isinstance(body, list):\n return self._process_list_body(body)\n return {}\n\n def _process_dict_body(self, body: dict) -> dict:\n \"\"\"Process dictionary body by parsing JSON values.\"\"\"\n return {k: self._parse_json_value(v) for k, v in body.items()}\n\n def _process_string_body(self, body: str) -> dict:\n \"\"\"Process string body by attempting JSON parse.\"\"\"\n try:\n return self._process_body(json.loads(body))\n except json.JSONDecodeError:\n return {\"data\": body}\n\n def _process_list_body(self, body: list) -> dict:\n \"\"\"Process list body by converting to key-value dictionary.\"\"\"\n processed_dict = {}\n try:\n for item in body:\n # Unwrap Data objects\n current_item = item\n if hasattr(item, \"data\"):\n unwrapped_data = item.data\n # If the unwrapped data is a dict but not key-value format, use it directly\n if isinstance(unwrapped_data, dict) and not self._is_valid_key_value_item(unwrapped_data):\n return unwrapped_data\n current_item = unwrapped_data\n if not self._is_valid_key_value_item(current_item):\n continue\n key = current_item[\"key\"]\n value = self._parse_json_value(current_item[\"value\"])\n processed_dict[key] = value\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process body list: {e}\")\n return {}\n return processed_dict\n\n def _is_valid_key_value_item(self, item: Any) -> bool:\n \"\"\"Check if an item is a valid key-value dictionary.\"\"\"\n return isinstance(item, dict) and \"key\" in item and \"value\" in item\n\n def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:\n \"\"\"Parse a cURL command and update build configuration.\"\"\"\n try:\n parsed = parse_context(curl)\n\n # Update basic configuration\n url = parsed.url\n # Normalize URL before setting it\n url = self._normalize_url(url)\n\n build_config[\"url_input\"][\"value\"] = url\n build_config[\"method\"][\"value\"] = parsed.method.upper()\n\n # Process headers\n headers_list = [{\"key\": k, \"value\": v} for k, v in parsed.headers.items()]\n build_config[\"headers\"][\"value\"] = headers_list\n\n # Process body data\n if not parsed.data:\n build_config[\"body\"][\"value\"] = []\n elif parsed.data:\n try:\n json_data = json.loads(parsed.data)\n if isinstance(json_data, dict):\n body_list = [\n {\"key\": k, \"value\": json.dumps(v) if isinstance(v, dict | list) else str(v)}\n for k, v in json_data.items()\n ]\n build_config[\"body\"][\"value\"] = body_list\n else:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": json.dumps(json_data)}]\n except json.JSONDecodeError:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": parsed.data}]\n\n except Exception as exc:\n msg = f\"Error parsing curl: {exc}\"\n self.log(msg)\n raise ValueError(msg) from exc\n\n return build_config\n\n def _normalize_url(self, url: str) -> str:\n \"\"\"Normalize URL by adding https:// if no protocol is specified.\"\"\"\n if not url or not isinstance(url, str):\n msg = \"URL cannot be empty\"\n raise ValueError(msg)\n\n url = url.strip()\n if url.startswith((\"http://\", \"https://\")):\n return url\n return f\"https://{url}\"\n\n async def make_request(\n self,\n client: httpx.AsyncClient,\n method: str,\n url: str,\n headers: dict | None = None,\n body: Any = None,\n timeout: int = 5,\n *,\n follow_redirects: bool = True,\n save_to_file: bool = False,\n include_httpx_metadata: bool = False,\n ) -> Data:\n method = method.upper()\n if method not in {\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"}:\n msg = f\"Unsupported method: {method}\"\n raise ValueError(msg)\n\n processed_body = self._process_body(body)\n redirection_history = []\n\n try:\n # Prepare request parameters\n request_params = {\n \"method\": method,\n \"url\": url,\n \"headers\": headers,\n \"json\": processed_body,\n \"timeout\": timeout,\n \"follow_redirects\": follow_redirects,\n }\n response = await client.request(**request_params)\n\n redirection_history = [\n {\n \"url\": redirect.headers.get(\"Location\", str(redirect.url)),\n \"status_code\": redirect.status_code,\n }\n for redirect in response.history\n ]\n\n is_binary, file_path = await self._response_info(response, with_file_path=save_to_file)\n response_headers = self._headers_to_dict(response.headers)\n\n # Base metadata\n metadata = {\n \"source\": url,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n }\n\n if redirection_history:\n metadata[\"redirection_history\"] = redirection_history\n\n if save_to_file:\n mode = \"wb\" if is_binary else \"w\"\n encoding = response.encoding if mode == \"w\" else None\n if file_path:\n await aiofiles_os.makedirs(file_path.parent, exist_ok=True)\n if is_binary:\n async with aiofiles.open(file_path, \"wb\") as f:\n await f.write(response.content)\n await f.flush()\n else:\n async with aiofiles.open(file_path, \"w\", encoding=encoding) as f:\n await f.write(response.text)\n await f.flush()\n metadata[\"file_path\"] = str(file_path)\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n return Data(data=metadata)\n\n # Handle response content\n if is_binary:\n result = response.content\n else:\n try:\n result = response.json()\n except json.JSONDecodeError:\n self.log(\"Failed to decode JSON response\")\n result = response.text.encode(\"utf-8\")\n\n metadata[\"result\"] = result\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n\n return Data(data=metadata)\n except (httpx.HTTPError, httpx.RequestError, httpx.TimeoutException) as exc:\n self.log(f\"Error making request to {url}\")\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 500,\n \"error\": str(exc),\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n },\n )\n\n def add_query_params(self, url: str, params: dict) -> str:\n \"\"\"Add query parameters to URL efficiently.\"\"\"\n if not params:\n return url\n url_parts = list(urlparse(url))\n query = dict(parse_qsl(url_parts[4]))\n query.update(params)\n url_parts[4] = urlencode(query)\n return urlunparse(url_parts)\n\n def _headers_to_dict(self, headers: httpx.Headers) -> dict[str, str]:\n \"\"\"Convert HTTP headers to a dictionary with lowercased keys.\"\"\"\n return {k.lower(): v for k, v in headers.items()}\n\n def _process_headers(self, headers: Any) -> dict:\n \"\"\"Process the headers input into a valid dictionary.\"\"\"\n if headers is None:\n return {}\n if isinstance(headers, dict):\n return headers\n if isinstance(headers, list):\n return {item[\"key\"]: item[\"value\"] for item in headers if self._is_valid_key_value_item(item)}\n return {}\n\n async def make_api_request(self) -> Data:\n \"\"\"Make HTTP request with optimized parameter handling.\"\"\"\n method = self.method\n url = self.url_input.strip() if isinstance(self.url_input, str) else \"\"\n headers = self.headers or {}\n body = self.body or {}\n timeout = self.timeout\n follow_redirects = self.follow_redirects\n save_to_file = self.save_to_file\n include_httpx_metadata = self.include_httpx_metadata\n\n # Security warning when redirects are enabled\n if follow_redirects:\n self.log(\n \"Security Warning: HTTP redirects are enabled. This may allow SSRF bypass attacks \"\n \"where a public URL redirects to internal resources (e.g., cloud metadata endpoints). \"\n \"Only enable this if you trust the target server.\"\n )\n\n # if self.mode == \"cURL\" and self.curl_input:\n # self._build_config = self.parse_curl(self.curl_input, dotdict())\n # # After parsing curl, get the normalized URL\n # url = self._build_config[\"url_input\"][\"value\"]\n\n # Normalize URL before validation\n url = self._normalize_url(url)\n\n # Validate URL\n if not validators.url(url):\n msg = f\"Invalid URL provided: {url}\"\n raise ValueError(msg)\n\n # SSRF Protection: Validate URL to prevent access to internal resources\n # TODO: In next major version (2.0), remove warn_only=True to enforce blocking\n try:\n validate_url_for_ssrf(url, warn_only=True)\n except SSRFProtectionError as e:\n # This will only raise if SSRF protection is enabled and warn_only=False\n msg = f\"SSRF Protection: {e}\"\n raise ValueError(msg) from e\n\n # Process query parameters\n if isinstance(self.query_params, str):\n query_params = dict(parse_qsl(self.query_params))\n else:\n query_params = self.query_params.data if self.query_params else {}\n\n # Process headers and body\n headers = self._process_headers(headers)\n body = self._process_body(body)\n url = self.add_query_params(url, query_params)\n\n async with httpx.AsyncClient() as client:\n result = await self.make_request(\n client,\n method,\n url,\n headers,\n body,\n timeout,\n follow_redirects=follow_redirects,\n save_to_file=save_to_file,\n include_httpx_metadata=include_httpx_metadata,\n )\n self.status = result\n return result\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n \"\"\"Update the build config based on the selected mode.\"\"\"\n if field_name != \"mode\":\n if field_name == \"curl_input\" and self.mode == \"cURL\" and self.curl_input:\n return self.parse_curl(self.curl_input, build_config)\n return build_config\n\n # print(f\"Current mode: {field_value}\")\n if field_value == \"cURL\":\n set_field_display(build_config, \"curl_input\", value=True)\n if build_config[\"curl_input\"][\"value\"]:\n build_config = self.parse_curl(build_config[\"curl_input\"][\"value\"], build_config)\n else:\n set_field_display(build_config, \"curl_input\", value=False)\n\n return set_current_fields(\n build_config=build_config,\n action_fields=MODE_FIELDS,\n selected_action=field_value,\n default_fields=DEFAULT_FIELDS,\n func=set_field_advanced,\n default_value=True,\n )\n\n async def _response_info(\n self, response: httpx.Response, *, with_file_path: bool = False\n ) -> tuple[bool, Path | None]:\n \"\"\"Determine the file path and whether the response content is binary.\n\n Args:\n response (Response): The HTTP response object.\n with_file_path (bool): Whether to save the response content to a file.\n\n Returns:\n Tuple[bool, Path | None]:\n A tuple containing a boolean indicating if the content is binary and the full file path (if applicable).\n \"\"\"\n content_type = response.headers.get(\"Content-Type\", \"\")\n is_binary = \"application/octet-stream\" in content_type or \"application/binary\" in content_type\n\n if not with_file_path:\n return is_binary, None\n\n component_temp_dir = Path(tempfile.gettempdir()) / self.__class__.__name__\n\n # Create directory asynchronously\n await aiofiles_os.makedirs(component_temp_dir, exist_ok=True)\n\n filename = None\n if \"Content-Disposition\" in response.headers:\n content_disposition = response.headers[\"Content-Disposition\"]\n filename_match = re.search(r'filename=\"(.+?)\"', content_disposition)\n if filename_match:\n extracted_filename = filename_match.group(1)\n filename = extracted_filename\n\n # Step 3: Infer file extension or use part of the request URL if no filename\n if not filename:\n # Extract the last segment of the URL path\n url_path = urlparse(str(response.request.url) if response.request else \"\").path\n base_name = Path(url_path).name # Get the last segment of the path\n if not base_name: # If the path ends with a slash or is empty\n base_name = \"response\"\n\n # Infer file extension\n content_type_to_extension = {\n \"text/plain\": \".txt\",\n \"application/json\": \".json\",\n \"image/jpeg\": \".jpg\",\n \"image/png\": \".png\",\n \"application/octet-stream\": \".bin\",\n }\n extension = content_type_to_extension.get(content_type, \".bin\" if is_binary else \".txt\")\n filename = f\"{base_name}{extension}\"\n\n # Step 4: Define the full file path\n file_path = component_temp_dir / filename\n\n # Step 5: Check if file exists asynchronously and handle accordingly\n try:\n # Try to create the file exclusively (x mode) to check existence\n async with aiofiles.open(file_path, \"x\") as _:\n pass # File created successfully, we can use this path\n except FileExistsError:\n # If file exists, append a timestamp to the filename\n timestamp = datetime.now(timezone.utc).strftime(\"%Y%m%d%H%M%S%f\")\n file_path = component_temp_dir / f\"{timestamp}-{filename}\"\n\n return is_binary, file_path\n" + "value": "import json\nimport re\nimport tempfile\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Any\nfrom urllib.parse import parse_qsl, urlencode, urlparse, urlunparse\n\nimport aiofiles\nimport aiofiles.os as aiofiles_os\nimport httpx\nimport validators\n\nfrom lfx.base.curl.parse import parse_context\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import TabInput\nfrom lfx.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n IntInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.component_utils import set_current_fields, set_field_advanced, set_field_display\nfrom lfx.utils.ssrf_protection import SSRFProtectionError, validate_url_for_ssrf\n\n# Define fields for each mode\nMODE_FIELDS = {\n \"URL\": [\n \"url_input\",\n \"method\",\n ],\n \"cURL\": [\"curl_input\"],\n}\n\n# Fields that should always be visible\nDEFAULT_FIELDS = [\"mode\"]\n\n\nclass APIRequestComponent(Component):\n display_name = \"API Request\"\n description = \"Make HTTP requests using URL or cURL commands.\"\n documentation: str = \"https://docs.langflow.org/api-request\"\n icon = \"Globe\"\n name = \"APIRequest\"\n\n inputs = [\n MessageTextInput(\n name=\"url_input\",\n display_name=\"URL\",\n info=\"Enter the URL for the request.\",\n advanced=False,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"curl_input\",\n display_name=\"cURL\",\n info=(\n \"Paste a curl command to populate the fields. \"\n \"This will fill in the dictionary fields for headers and body.\"\n ),\n real_time_refresh=True,\n tool_mode=True,\n advanced=True,\n show=False,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Method\",\n options=[\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"],\n value=\"GET\",\n info=\"The HTTP method to use.\",\n real_time_refresh=True,\n ),\n TabInput(\n name=\"mode\",\n display_name=\"Mode\",\n options=[\"URL\", \"cURL\"],\n value=\"URL\",\n info=\"Enable cURL mode to populate fields from a cURL command.\",\n real_time_refresh=True,\n ),\n DataInput(\n name=\"query_params\",\n display_name=\"Query Parameters\",\n info=\"The query parameters to append to the URL.\",\n advanced=True,\n ),\n TableInput(\n name=\"body\",\n display_name=\"Body\",\n info=\"The body to send with the request as a dictionary (for POST, PATCH, PUT).\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Parameter name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"description\": \"Parameter value\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n advanced=True,\n real_time_refresh=True,\n ),\n TableInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"The headers to send with the request\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Header\",\n \"type\": \"str\",\n \"description\": \"Header name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Header value\",\n },\n ],\n value=[{\"key\": \"User-Agent\", \"value\": \"Langflow/1.0\"}],\n advanced=True,\n input_types=[\"Data\"],\n real_time_refresh=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n value=30,\n info=\"The timeout to use for the request.\",\n advanced=True,\n ),\n BoolInput(\n name=\"follow_redirects\",\n display_name=\"Follow Redirects\",\n value=False,\n info=(\n \"Whether to follow HTTP redirects. \"\n \"WARNING: Enabling redirects may allow SSRF bypass attacks where a public URL \"\n \"redirects to internal resources. Only enable if you trust the target server. \"\n \"See OWASP SSRF Prevention Cheat Sheet for details.\"\n ),\n advanced=True,\n ),\n BoolInput(\n name=\"save_to_file\",\n display_name=\"Save to File\",\n value=False,\n info=\"Save the API response to a temporary file\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_httpx_metadata\",\n display_name=\"Include HTTPx Metadata\",\n value=False,\n info=(\n \"Include properties such as headers, status_code, response_headers, \"\n \"and redirection_history in the output.\"\n ),\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"API Response\", name=\"data\", method=\"make_api_request\"),\n ]\n\n def _parse_json_value(self, value: Any) -> Any:\n \"\"\"Parse a value that might be a JSON string.\"\"\"\n if not isinstance(value, str):\n return value\n\n try:\n parsed = json.loads(value)\n except json.JSONDecodeError:\n return value\n else:\n return parsed\n\n def _process_body(self, body: Any) -> dict:\n \"\"\"Process the body input into a valid dictionary.\"\"\"\n if body is None:\n return {}\n if hasattr(body, \"data\"):\n body = body.data\n if isinstance(body, dict):\n return self._process_dict_body(body)\n if isinstance(body, str):\n return self._process_string_body(body)\n if isinstance(body, list):\n return self._process_list_body(body)\n return {}\n\n def _process_dict_body(self, body: dict) -> dict:\n \"\"\"Process dictionary body by parsing JSON values.\"\"\"\n return {k: self._parse_json_value(v) for k, v in body.items()}\n\n def _process_string_body(self, body: str) -> dict:\n \"\"\"Process string body by attempting JSON parse.\"\"\"\n try:\n return self._process_body(json.loads(body))\n except json.JSONDecodeError:\n return {\"data\": body}\n\n def _process_list_body(self, body: list) -> dict:\n \"\"\"Process list body by converting to key-value dictionary.\"\"\"\n processed_dict = {}\n try:\n for item in body:\n # Unwrap Data objects\n current_item = item\n if hasattr(item, \"data\"):\n unwrapped_data = item.data\n # If the unwrapped data is a dict but not key-value format, use it directly\n if isinstance(unwrapped_data, dict) and not self._is_valid_key_value_item(unwrapped_data):\n return unwrapped_data\n current_item = unwrapped_data\n if not self._is_valid_key_value_item(current_item):\n continue\n key = current_item[\"key\"]\n value = self._parse_json_value(current_item[\"value\"])\n processed_dict[key] = value\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process body list: {e}\")\n return {}\n return processed_dict\n\n def _is_valid_key_value_item(self, item: Any) -> bool:\n \"\"\"Check if an item is a valid key-value dictionary.\"\"\"\n return isinstance(item, dict) and \"key\" in item and \"value\" in item\n\n def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:\n \"\"\"Parse a cURL command and update build configuration.\"\"\"\n try:\n parsed = parse_context(curl)\n\n # Update basic configuration\n url = parsed.url\n # Normalize URL before setting it\n url = self._normalize_url(url)\n\n build_config[\"url_input\"][\"value\"] = url\n build_config[\"method\"][\"value\"] = parsed.method.upper()\n\n # Process headers\n headers_list = [{\"key\": k, \"value\": v} for k, v in parsed.headers.items()]\n build_config[\"headers\"][\"value\"] = headers_list\n\n # Process body data\n if not parsed.data:\n build_config[\"body\"][\"value\"] = []\n elif parsed.data:\n try:\n json_data = json.loads(parsed.data)\n if isinstance(json_data, dict):\n body_list = [\n {\"key\": k, \"value\": json.dumps(v) if isinstance(v, dict | list) else str(v)}\n for k, v in json_data.items()\n ]\n build_config[\"body\"][\"value\"] = body_list\n else:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": json.dumps(json_data)}]\n except json.JSONDecodeError:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": parsed.data}]\n\n except Exception as exc:\n msg = f\"Error parsing curl: {exc}\"\n self.log(msg)\n raise ValueError(msg) from exc\n\n return build_config\n\n def _normalize_url(self, url: str) -> str:\n \"\"\"Normalize URL by adding https:// if no protocol is specified.\"\"\"\n if not url or not isinstance(url, str):\n msg = \"URL cannot be empty\"\n raise ValueError(msg)\n\n url = url.strip()\n if url.startswith((\"http://\", \"https://\")):\n return url\n return f\"https://{url}\"\n\n async def make_request(\n self,\n client: httpx.AsyncClient,\n method: str,\n url: str,\n headers: dict | None = None,\n body: Any = None,\n timeout: int = 5,\n *,\n follow_redirects: bool = True,\n save_to_file: bool = False,\n include_httpx_metadata: bool = False,\n ) -> Data:\n method = method.upper()\n if method not in {\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"}:\n msg = f\"Unsupported method: {method}\"\n raise ValueError(msg)\n\n processed_body = self._process_body(body)\n redirection_history = []\n\n try:\n # Prepare request parameters\n request_params = {\n \"method\": method,\n \"url\": url,\n \"headers\": headers,\n \"json\": processed_body,\n \"timeout\": timeout,\n \"follow_redirects\": follow_redirects,\n }\n response = await client.request(**request_params)\n\n redirection_history = [\n {\n \"url\": redirect.headers.get(\"Location\", str(redirect.url)),\n \"status_code\": redirect.status_code,\n }\n for redirect in response.history\n ]\n\n is_binary, file_path = await self._response_info(response, with_file_path=save_to_file)\n response_headers = self._headers_to_dict(response.headers)\n\n # Base metadata\n metadata = {\n \"source\": url,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n }\n\n if redirection_history:\n metadata[\"redirection_history\"] = redirection_history\n\n if save_to_file:\n mode = \"wb\" if is_binary else \"w\"\n encoding = response.encoding if mode == \"w\" else None\n if file_path:\n await aiofiles_os.makedirs(file_path.parent, exist_ok=True)\n if is_binary:\n async with aiofiles.open(file_path, \"wb\") as f:\n await f.write(response.content)\n await f.flush()\n else:\n async with aiofiles.open(file_path, \"w\", encoding=encoding) as f:\n await f.write(response.text)\n await f.flush()\n metadata[\"file_path\"] = str(file_path)\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n return Data(data=metadata)\n\n # Handle response content\n if is_binary:\n result = response.content\n else:\n try:\n result = response.json()\n except json.JSONDecodeError:\n self.log(\"Failed to decode JSON response\")\n result = response.text.encode(\"utf-8\")\n\n metadata[\"result\"] = result\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n\n return Data(data=metadata)\n except (httpx.HTTPError, httpx.RequestError, httpx.TimeoutException) as exc:\n self.log(f\"Error making request to {url}\")\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 500,\n \"error\": str(exc),\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n },\n )\n\n def add_query_params(self, url: str, params: dict) -> str:\n \"\"\"Add query parameters to URL efficiently.\"\"\"\n if not params:\n return url\n url_parts = list(urlparse(url))\n query = dict(parse_qsl(url_parts[4]))\n query.update(params)\n url_parts[4] = urlencode(query)\n return urlunparse(url_parts)\n\n def _headers_to_dict(self, headers: httpx.Headers) -> dict[str, str]:\n \"\"\"Convert HTTP headers to a dictionary with lowercased keys.\"\"\"\n return {k.lower(): v for k, v in headers.items()}\n\n def _process_headers(self, headers: Any) -> dict:\n \"\"\"Process the headers input into a valid dictionary.\"\"\"\n if headers is None:\n return {}\n if isinstance(headers, dict):\n return headers\n if isinstance(headers, list):\n return {item[\"key\"]: item[\"value\"] for item in headers if self._is_valid_key_value_item(item)}\n return {}\n\n async def make_api_request(self) -> Data:\n \"\"\"Make HTTP request with optimized parameter handling.\"\"\"\n method = self.method\n url = self.url_input.strip() if isinstance(self.url_input, str) else \"\"\n headers = self.headers or {}\n body = self.body or {}\n timeout = self.timeout\n follow_redirects = self.follow_redirects\n save_to_file = self.save_to_file\n include_httpx_metadata = self.include_httpx_metadata\n\n # Security warning when redirects are enabled\n if follow_redirects:\n self.log(\n \"Security Warning: HTTP redirects are enabled. This may allow SSRF bypass attacks \"\n \"where a public URL redirects to internal resources (e.g., cloud metadata endpoints). \"\n \"Only enable this if you trust the target server.\"\n )\n\n # if self.mode == \"cURL\" and self.curl_input:\n # self._build_config = self.parse_curl(self.curl_input, dotdict())\n # # After parsing curl, get the normalized URL\n # url = self._build_config[\"url_input\"][\"value\"]\n\n # Normalize URL before validation\n url = self._normalize_url(url)\n\n # Validate URL\n if not validators.url(url):\n msg = f\"Invalid URL provided: {url}\"\n raise ValueError(msg)\n\n # SSRF Protection: Validate URL to prevent access to internal resources\n # TODO: In next major version (2.0), remove warn_only=True to enforce blocking\n try:\n validate_url_for_ssrf(url, warn_only=True)\n except SSRFProtectionError as e:\n # This will only raise if SSRF protection is enabled and warn_only=False\n msg = f\"SSRF Protection: {e}\"\n raise ValueError(msg) from e\n\n # Process query parameters\n if isinstance(self.query_params, str):\n query_params = dict(parse_qsl(self.query_params))\n else:\n query_params = self.query_params.data if self.query_params else {}\n\n # Process headers and body\n headers = self._process_headers(headers)\n body = self._process_body(body)\n url = self.add_query_params(url, query_params)\n\n async with httpx.AsyncClient() as client:\n result = await self.make_request(\n client,\n method,\n url,\n headers,\n body,\n timeout,\n follow_redirects=follow_redirects,\n save_to_file=save_to_file,\n include_httpx_metadata=include_httpx_metadata,\n )\n self.status = result\n return result\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n \"\"\"Update the build config based on the selected mode.\"\"\"\n if field_name != \"mode\":\n if field_name == \"curl_input\" and self.mode == \"cURL\" and self.curl_input:\n return self.parse_curl(self.curl_input, build_config)\n return build_config\n\n if field_value == \"cURL\":\n set_field_display(build_config, \"curl_input\", value=True)\n if build_config[\"curl_input\"][\"value\"]:\n try:\n build_config = self.parse_curl(build_config[\"curl_input\"][\"value\"], build_config)\n except ValueError as e:\n self.log(f\"Failed to parse cURL input: {e}\")\n else:\n set_field_display(build_config, \"curl_input\", value=False)\n\n return set_current_fields(\n build_config=build_config,\n action_fields=MODE_FIELDS,\n selected_action=field_value,\n default_fields=DEFAULT_FIELDS,\n func=set_field_advanced,\n default_value=True,\n )\n\n async def _response_info(\n self, response: httpx.Response, *, with_file_path: bool = False\n ) -> tuple[bool, Path | None]:\n \"\"\"Determine the file path and whether the response content is binary.\n\n Args:\n response (Response): The HTTP response object.\n with_file_path (bool): Whether to save the response content to a file.\n\n Returns:\n Tuple[bool, Path | None]:\n A tuple containing a boolean indicating if the content is binary and the full file path (if applicable).\n \"\"\"\n content_type = response.headers.get(\"Content-Type\", \"\")\n is_binary = \"application/octet-stream\" in content_type or \"application/binary\" in content_type\n\n if not with_file_path:\n return is_binary, None\n\n component_temp_dir = Path(tempfile.gettempdir()) / self.__class__.__name__\n\n # Create directory asynchronously\n await aiofiles_os.makedirs(component_temp_dir, exist_ok=True)\n\n filename = None\n if \"Content-Disposition\" in response.headers:\n content_disposition = response.headers[\"Content-Disposition\"]\n filename_match = re.search(r'filename=\"(.+?)\"', content_disposition)\n if filename_match:\n extracted_filename = filename_match.group(1)\n filename = extracted_filename\n\n # Step 3: Infer file extension or use part of the request URL if no filename\n if not filename:\n # Extract the last segment of the URL path\n url_path = urlparse(str(response.request.url) if response.request else \"\").path\n base_name = Path(url_path).name # Get the last segment of the path\n if not base_name: # If the path ends with a slash or is empty\n base_name = \"response\"\n\n # Infer file extension\n content_type_to_extension = {\n \"text/plain\": \".txt\",\n \"application/json\": \".json\",\n \"image/jpeg\": \".jpg\",\n \"image/png\": \".png\",\n \"application/octet-stream\": \".bin\",\n }\n extension = content_type_to_extension.get(content_type, \".bin\" if is_binary else \".txt\")\n filename = f\"{base_name}{extension}\"\n\n # Step 4: Define the full file path\n file_path = component_temp_dir / filename\n\n # Step 5: Check if file exists asynchronously and handle accordingly\n try:\n # Try to create the file exclusively (x mode) to check existence\n async with aiofiles.open(file_path, \"x\") as _:\n pass # File created successfully, we can use this path\n except FileExistsError:\n # If file exists, append a timestamp to the filename\n timestamp = datetime.now(timezone.utc).strftime(\"%Y%m%d%H%M%S%f\")\n file_path = component_temp_dir / f\"{timestamp}-{filename}\"\n\n return is_binary, file_path\n" }, "curl_input": { "_input_type": "MultilineInput", @@ -1236,13 +1236,9 @@ "key": "Agent", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1250,6 +1246,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1323,71 +1323,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1400,27 +1341,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1437,7 +1357,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1546,137 +1466,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1696,27 +1521,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1779,47 +1583,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1845,56 +1608,6 @@ "type": "str", "value": "You are a pokedex. Grab information about pokemons using the following endpoint:\nhttps://pokeapi.co/api/v2/pokemon/\n\nFor example:\nhttps://pokeapi.co/api/v2/pokemon/ditto\nhttps://pokeapi.co/api/v2/pokemon/pikachu\n\nFix user pokemon name mispelling." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json index 94476cce85ac..0dfd57990ed7 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json @@ -326,7 +326,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -402,7 +402,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1528,7 +1528,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -1855,7 +1855,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -2101,7 +2101,7 @@ "legacy": false, "lf_version": "1.6.0", "metadata": { - "code_hash": "ce82f9d7948c", + "code_hash": "058ca1f51e9f", "dependencies": { "dependencies": [ { @@ -2156,6 +2156,26 @@ "pinned": false, "template": { "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": true, + "display_name": "API Key", + "dynamic": false, + "info": "Model Provider API key", + "input_types": [], + "load_from_db": true, + "name": "api_key", + "override_skip": false, + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "track_in_telemetry": false, + "type": "str", + "value": "" + }, "code": { "advanced": true, "dynamic": true, @@ -2172,7 +2192,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n HandleInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(self.llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = self.llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" + "value": "from pydantic import BaseModel, Field, create_model\nfrom trustcall import create_extractor\n\nfrom lfx.base.models.chat_result import get_chat_result\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.io import (\n MessageTextInput,\n ModelInput,\n MultilineInput,\n Output,\n SecretStrInput,\n TableInput,\n)\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.table import EditMode\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = \"Uses an LLM to generate structured data. Ideal for extraction and consistency.\"\n documentation: str = \"https://docs.langflow.org/structured-output\"\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"dataframe_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_dataframe\",\n ),\n ]\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n def build_structured_output_base(self):\n schema_name = self.schema_name or \"OutputModel\"\n\n llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)\n\n if not hasattr(llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n output_model = create_model(\n schema_name,\n __doc__=f\"A list of {schema_name}.\",\n objects=(\n list[output_model_],\n Field(\n description=f\"A list of {schema_name}.\", # type: ignore[valid-type]\n min_length=1, # help ensure non-empty output\n ),\n ),\n )\n # Tracing config\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n # Generate structured output using Trustcall first, then fallback to Langchain if it fails\n result = self._extract_output_with_trustcall(llm, output_model, config_dict)\n if result is None:\n result = self._extract_output_with_langchain(llm, output_model, config_dict)\n\n # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure\n # Handle non-dict responses (shouldn't happen with trustcall, but defensive)\n if not isinstance(result, dict):\n return result\n\n # Extract first response and convert BaseModel to dict\n responses = result.get(\"responses\", [])\n if not responses:\n return result\n\n # Convert BaseModel to dict (creates the \"objects\" key)\n first_response = responses[0]\n structured_data = first_response\n if isinstance(first_response, BaseModel):\n structured_data = first_response.model_dump()\n # Extract the objects array (guaranteed to exist due to our Pydantic model structure)\n return structured_data.get(\"objects\", structured_data)\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n return Data(data=output[0])\n if len(output) > 1:\n # Multiple outputs - wrap them in a results container\n return Data(data={\"results\": output})\n return Data()\n\n def build_structured_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if not isinstance(output, list) or not output:\n # handle empty or unexpected type case\n msg = \"No structured output returned\"\n raise ValueError(msg)\n if len(output) == 1:\n # For single dictionary, wrap in a list to create DataFrame with one row\n return DataFrame([output[0]])\n if len(output) > 1:\n # Multiple outputs - convert to DataFrame directly\n return DataFrame(output)\n return DataFrame()\n\n def _extract_output_with_trustcall(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = create_extractor(llm, tools=[schema], tool_choice=schema.__name__)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n except Exception as e: # noqa: BLE001\n logger.warning(\n f\"Trustcall extraction failed, falling back to Langchain: {e} \"\n \"(Note: This may not be an error—some models or configurations do not support tool calling. \"\n \"Falling back is normal in such cases.)\"\n )\n return None\n return result or None # langchain fallback is used if error occurs or the result is empty\n\n def _extract_output_with_langchain(self, llm, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:\n try:\n llm_with_structured_output = llm.with_structured_output(schema)\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n result = result.get(\"objects\", result)\n except Exception as fallback_error:\n msg = (\n f\"Model does not support tool calling (trustcall failed) \"\n f\"and fallback with_structured_output also failed: {fallback_error}\"\n )\n raise ValueError(msg) from fallback_error\n\n return result or None\n" }, "input_value": { "_input_type": "MultilineInput", @@ -2199,26 +2219,41 @@ "type": "str", "value": "" }, - "llm": { - "_input_type": "HandleInput", + "model": { + "_input_type": "ModelInput", "advanced": false, "display_name": "Language Model", "dynamic": false, - "info": "The language model to use to generate the structured output.", + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } + }, + "info": "Select your model provider", "input_types": [ "LanguageModel" ], "list": false, "list_add_label": "Add More", - "name": "llm", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, "required": true, "show": true, "title_case": false, - "trace_as_metadata": true, + "tool_mode": false, + "trace_as_input": true, "track_in_telemetry": false, - "type": "other", + "type": "model", "value": "" }, "output_schema": { diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json index 8c72ba11a167..711266576eb9 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json @@ -416,7 +416,7 @@ "legacy": false, "lf_version": "1.3.2", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -492,7 +492,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1603,13 +1603,9 @@ "key": "Agent", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1617,6 +1613,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1690,71 +1690,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1767,27 +1708,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1804,7 +1724,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1913,137 +1833,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -2063,27 +1888,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -2146,47 +1950,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -2212,56 +1975,6 @@ "type": "str", "value": "You are an deal finder assistant that helps find and compare the prices of products across different e-commerce platforms. You must use the Tavily Search API to find the URLs of the ecommerce platforms that sell these products. Then use the AgentQL tool to extract the prices of the product in those websites. Make sure to include the name of the product, the price of the product, the shop name, and the URL link of the page to where you can add the product to a cart or checkout immediately. The price and URL link has to be retrieved, so if it's not available or doesn't work don't include it.\n\nHere's how to write an AgentQL query:\n\nThe AgentQL query serves as the building block of your script. This guide shows you how AgentQL's query structure works and how to write a valid query.\n\n### Single term query\n\nA **single term query** enables you to retrieve a single element on the webpage. Here is an example of how you can write a single term query to retrieve a search box.\n\n```AgentQL\n{\n search_box\n}\n```\n\n### List term query\n\nA **list term query** enables you to retrieve a list of similar elements on the webpage. Here is an example of how you can write a list term query to retrieve a list of prices of apples.\n\n```AgentQL\n{\n apple_price[]\n}\n```\n\nYou can also specify the exact field you want to return in the list. Here is an example of how you can specify that you want the name and price from the list of products.\n\n```AgentQL\n{\n products[] {\n name\n price(integer)\n }\n}\n```\n\n### Combining single term queries and list term queries\n\nYou can query for both **single terms** and **list terms** by combining the preceding formats.\n\n```AgentQL\n{\n author\n date_of_birth\n book_titles[]\n}\n```\n\n### Giving context to queries\n\nThere two main ways you can provide additional context to your queries.\n\n#### Structural context\n\nYou can nest queries within parent containers to indicate that your target web element is in a particular section of the webpage.\n\n```AgentQL\n{\n footer {\n social_media_links[]\n }\n}\n```\n\n#### Semantic context\n\nYou can also provide a short description within parentheses to guide AgentQL in locating the right element(s).\n\n```AgentQL\n{\n footer {\n social_media_links(The icons that lead to Facebook, Snapchat, etc.)[]\n }\n}\n```\n\n### Syntax guidelines\n\nEnclose all AgentQL query terms within curly braces `{}`. The following query structure isn't valid because the term \"social_media_links\" is wrongly enclosed within parenthesis`()`.\n\n```AgentQL\n( # Should be {\n social_media_links(The icons that lead to Facebook, Snapchat, etc.)[]\n) # Should be }\n```\n\nYou can't include new lines in your semantic context. The following query structure isn't valid because the semantic context isn't contained within one line.\n\n```AgentQL\n{\n social_media_links(The icons that lead\n to Facebook, Snapchat, etc.)[]\n}\n```" }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json index 91f7e80a870b..654be02cc17d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json @@ -1631,7 +1631,7 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -1707,7 +1707,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -2010,7 +2010,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -2331,7 +2331,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -2566,13 +2566,9 @@ "key": "Agent", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -2580,6 +2576,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -2653,71 +2653,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -2730,27 +2671,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -2767,7 +2687,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -2876,137 +2796,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -3026,27 +2851,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -3109,47 +2913,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -3175,56 +2938,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json b/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json index d624bbb11505..3cd298d4c0c0 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Research Translation Loop.json @@ -402,7 +402,7 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -477,7 +477,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1404,7 +1404,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json index ae5de58df2e1..28206d0989f8 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SEO Keyword Generator.json @@ -558,7 +558,7 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -632,7 +632,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -959,7 +959,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json index 8aa6372de121..ecbd3c319207 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json @@ -368,7 +368,7 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -443,7 +443,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -861,13 +861,9 @@ "key": "Agent", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -875,6 +871,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -948,71 +948,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1025,27 +966,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1062,7 +982,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1171,137 +1091,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1321,27 +1146,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1404,47 +1208,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1470,56 +1233,6 @@ "type": "str", "value": "# Subscription Pricing Calculator\n\n## Purpose\nCalculate the optimal monthly subscription price for a software product based on operational costs, desired profit margin, and estimated subscriber base.\n\n## Input Variables\nThe system requires the following inputs:\n- Monthly infrastructure costs (numeric)\n- Customer support costs (numeric)\n- Continuous development costs (numeric)\n- Desired profit margin (percentage)\n- Estimated number of subscribers (numeric)\n\n## Calculation Process\nFollow these steps to determine the subscription price:\n\n### Step 1: Total Monthly Costs\nCalculate the sum of all fixed operational costs:\n```\ntotal_monthly_costs = infrastructure_costs + support_costs + development_costs\n```\n\n### Step 2: Profit Margin Calculation\nCalculate the profit margin amount based on total costs:\n```\nprofit_amount = total_monthly_costs × (profit_margin_percentage / 100)\n```\n\n### Step 3: Total Revenue Required\nCalculate the total monthly revenue needed:\n```\ntotal_revenue_needed = total_monthly_costs + profit_amount\n```\n\n### Step 4: Per-Subscriber Price\nCalculate the minimum price per subscriber:\n```\nsubscription_price = total_revenue_needed ÷ estimated_subscribers\n```\n\n## Output Format\nPresent the results in the following structure:\n\nFixed costs: [sum of all costs]\nProfit margin: [calculated profit amount]\nTotal amount needed: [total revenue required]\nPrice per subscriber: [calculated subscription price]\n\nFinal recommendation: \"The minimum subscription price per subscriber should be [price] to achieve the desired profit margin of [percentage]%\"\n\n## Notes\n- All monetary values should be rounded to 2 decimal places\n- Ensure all input values are positive numbers\n- Validate that the estimated subscribers count is greater than zero\n- The profit margin percentage should be between 0 and 100" }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json index 30d367a08590..76daa632cbb5 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json @@ -567,7 +567,7 @@ "legacy": false, "lf_version": "1.1.5", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -642,7 +642,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -942,13 +942,9 @@ "key": "Agent", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -956,6 +952,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1029,71 +1029,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1106,27 +1047,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1143,7 +1063,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1252,137 +1172,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1402,27 +1227,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1485,47 +1289,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1551,56 +1314,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json index 161fae0c31cf..9ba6e71c197a 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json @@ -357,13 +357,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -371,6 +367,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -441,71 +441,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -518,27 +459,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -555,7 +475,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -664,137 +584,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -814,27 +639,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -897,47 +701,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -963,56 +726,6 @@ "type": "str", "value": "You are the chief editor of a prestigious publication known for transforming complex information into clear, engaging content. Review and refine the researcher's document about {topic}.\n\nYour editing process should:\n- Verify and challenge any questionable claims\n- Restructure content for better flow and readability\n- Remove redundancies and unclear statements\n- Add context where needed\n- Ensure balanced coverage of the topic\n- Transform technical language into accessible explanations\n\nMaintain high editorial standards while making the content engaging for an educated general audience. Present the revised version in a clean, well-structured format." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -1116,13 +829,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1130,6 +839,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1200,71 +913,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1277,27 +931,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1314,7 +947,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1423,137 +1056,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1573,27 +1111,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1634,68 +1151,27 @@ "str", "int", "float", - "bool", - "dict" - ], - "type": "str" - }, - { - "default": "False", - "description": "Set to True if this output field should be a list of the specified type.", - "display_name": "As List", - "edit_mode": "inline", - "name": "multiple", - "type": "boolean" - } - ], - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "trigger_icon": "Table", - "trigger_text": "Open table", - "type": "table", - "value": [] - }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, + "bool", + "dict" + ], + "type": "str" + }, + { + "default": "False", + "description": "Set to True if this output field should be a list of the specified type.", + "display_name": "As List", + "edit_mode": "inline", + "name": "multiple", + "type": "boolean" + } + ], "title_case": false, "tool_mode": false, "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": 1 + "trigger_icon": "Table", + "trigger_text": "Open table", + "type": "table", + "value": [] }, "system_prompt": { "_input_type": "MultilineInput", @@ -1722,56 +1198,6 @@ "type": "str", "value": "You are a brilliant comedy writer known for making complex topics entertaining and memorable. Using the editor's refined document about {topic}, create an engaging, humorous blog post.\n\nYour approach should:\n- Find unexpected angles and amusing parallels\n- Use clever wordplay and wit (avoid cheap jokes)\n- Maintain accuracy while being entertaining\n- Include relatable examples and analogies\n- Keep a smart, sophisticated tone\n- Make the topic more approachable through humor\n\nCreate a blog post that makes people laugh while actually teaching them about {topic}. The humor should enhance, not overshadow, the educational value." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -2638,13 +2064,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -2652,6 +2074,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -2722,71 +2148,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -2799,27 +2166,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -2836,7 +2182,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -2945,137 +2291,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -3095,27 +2346,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -3178,47 +2408,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -3244,56 +2433,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -4196,7 +3335,7 @@ "key": "ChatOutput", "legacy": false, "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -4272,7 +3411,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json index c4bf3d68b919..36b8de36f756 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json @@ -639,7 +639,7 @@ "key": "ChatOutput", "legacy": false, "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -715,7 +715,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -930,13 +930,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -944,6 +940,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1014,71 +1014,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1091,27 +1032,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1128,7 +1048,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1237,137 +1157,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1387,27 +1212,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1470,47 +1274,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1536,56 +1299,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json index ecf418a6d6d7..c6067b99a86b 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json @@ -971,7 +971,7 @@ "legacy": false, "lf_version": "1.4.2", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -1046,7 +1046,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1288,13 +1288,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1302,6 +1298,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1372,71 +1372,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1449,27 +1390,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1486,7 +1406,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1595,137 +1515,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1745,27 +1570,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1828,47 +1632,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1894,56 +1657,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json index f096ee2ddef0..333dd132b61c 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json @@ -713,7 +713,7 @@ "icon": "MessagesSquare", "legacy": false, "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -788,7 +788,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -995,7 +995,7 @@ "icon": "MessagesSquare", "legacy": false, "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -1070,7 +1070,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1463,7 +1463,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -1791,7 +1791,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", @@ -2118,7 +2118,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json index c2afaf8dc479..f76d7caedc86 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json @@ -496,7 +496,7 @@ "legacy": false, "lf_version": "1.2.0", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -570,7 +570,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1658,13 +1658,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -1672,6 +1668,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -1742,71 +1742,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -1819,27 +1760,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -1856,7 +1776,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1965,137 +1885,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -2115,27 +1940,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -2198,47 +2002,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -2264,56 +2027,6 @@ "type": "str", "value": "You are a helpful assistant that can use tools to answer questions and perform tasks." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -2412,13 +2125,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -2426,6 +2135,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -2496,71 +2209,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", + "api_key": { + "_input_type": "SecretStrInput", "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", + "display_name": "API Key", "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, - "api_key": { - "_input_type": "SecretStrInput", - "advanced": false, - "display_name": "OpenAI API Key", - "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -2573,27 +2227,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -2610,7 +2243,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -2719,137 +2352,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -2869,27 +2407,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -2952,47 +2469,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -3018,56 +2494,6 @@ "type": "str", "value": "You are a knowledgeable Local Expert with extensive information about the selected city, its attractions, and customs. Your goal is to provide the BEST insights about the city. Compile an in-depth guide for travelers, including key attractions, local customs, special events, and daily activity recommendations. Focus on hidden gems and local hotspots. Your final output should be a comprehensive city guide, rich in cultural insights and practical tips." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -3166,13 +2592,9 @@ "icon": "bot", "legacy": false, "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -3180,6 +2602,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -3250,71 +2676,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -3327,27 +2694,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -3364,7 +2710,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -3473,137 +2819,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -3623,27 +2874,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -3706,47 +2936,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -3772,56 +2961,6 @@ "type": "str", "value": "You are an Amazing Travel Concierge, a specialist in travel planning and logistics with decades of experience. Your goal is to create the most amazing travel itineraries with budget and packing suggestions for the city. Expand the city guide into a full 7-day travel itinerary with detailed per-day plans. Include weather forecasts, places to eat, packing suggestions, and a budget breakdown. Suggest actual places to visit, hotels to stay, and restaurants to go to. Your final output should be a complete expanded travel plan, formatted as markdown, encompassing a daily schedule, anticipated weather conditions, recommended clothing and items to pack, and a detailed budget." }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json index c28146688421..86e237f29187 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Twitter Thread Generator.json @@ -681,7 +681,7 @@ "icon": "MessagesSquare", "legacy": false, "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -756,7 +756,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1948,7 +1948,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json index 901fdeed9578..4483a5bbd8b6 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json @@ -1057,7 +1057,7 @@ "legacy": false, "lf_version": "1.1.1", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -1131,7 +1131,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -3258,7 +3258,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json index 8a70632dedb9..f57d329f264c 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -285,7 +285,7 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "14406c29c609", + "code_hash": "53935e54dfe7", "dependencies": { "dependencies": [ { @@ -327,6 +327,26 @@ "score": 0.007568328950209746, "template": { "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": true, + "display_name": "API Key", + "dynamic": false, + "info": "Model Provider API key", + "input_types": [], + "load_from_db": true, + "name": "api_key", + "override_skip": false, + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "track_in_telemetry": false, + "type": "str", + "value": "" + }, "code": { "advanced": true, "dynamic": true, @@ -343,7 +363,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, cast\n\nimport toml # type: ignore[import-untyped]\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DataFrameInput, HandleInput, MessageTextInput, MultilineInput, Output\nfrom lfx.log.logger import logger\nfrom lfx.schema.dataframe import DataFrame\n\nif TYPE_CHECKING:\n from langchain_core.runnables import Runnable\n\n\nclass BatchRunComponent(Component):\n display_name = \"Batch Run\"\n description = \"Runs an LLM on each row of a DataFrame column. If no column is specified, all columns are used.\"\n documentation: str = \"https://docs.langflow.org/batch-run\"\n icon = \"List\"\n\n inputs = [\n HandleInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Connect the 'Language Model' output from your LLM component here.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"Instructions\",\n info=\"Multi-line system instruction for all rows in the DataFrame.\",\n required=False,\n ),\n DataFrameInput(\n name=\"df\",\n display_name=\"DataFrame\",\n info=\"The DataFrame whose column (specified by 'column_name') we'll treat as text messages.\",\n required=True,\n ),\n MessageTextInput(\n name=\"column_name\",\n display_name=\"Column Name\",\n info=(\n \"The name of the DataFrame column to treat as text messages. \"\n \"If empty, all columns will be formatted in TOML.\"\n ),\n required=False,\n advanced=False,\n ),\n MessageTextInput(\n name=\"output_column_name\",\n display_name=\"Output Column Name\",\n info=\"Name of the column where the model's response will be stored.\",\n value=\"model_response\",\n required=False,\n advanced=True,\n ),\n BoolInput(\n name=\"enable_metadata\",\n display_name=\"Enable Metadata\",\n info=\"If True, add metadata to the output DataFrame.\",\n value=False,\n required=False,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"LLM Results\",\n name=\"batch_results\",\n method=\"run_batch\",\n info=\"A DataFrame with all original columns plus the model's response column.\",\n ),\n ]\n\n def _format_row_as_toml(self, row: dict[str, Any]) -> str:\n \"\"\"Convert a dictionary (row) into a TOML-formatted string.\"\"\"\n formatted_dict = {str(col): {\"value\": str(val)} for col, val in row.items()}\n return toml.dumps(formatted_dict)\n\n def _create_base_row(\n self, original_row: dict[str, Any], model_response: str = \"\", batch_index: int = -1\n ) -> dict[str, Any]:\n \"\"\"Create a base row with original columns and additional metadata.\"\"\"\n row = original_row.copy()\n row[self.output_column_name] = model_response\n row[\"batch_index\"] = batch_index\n return row\n\n def _add_metadata(\n self, row: dict[str, Any], *, success: bool = True, system_msg: str = \"\", error: str | None = None\n ) -> None:\n \"\"\"Add metadata to a row if enabled.\"\"\"\n if not self.enable_metadata:\n return\n\n if success:\n row[\"metadata\"] = {\n \"has_system_message\": bool(system_msg),\n \"input_length\": len(row.get(\"text_input\", \"\")),\n \"response_length\": len(row[self.output_column_name]),\n \"processing_status\": \"success\",\n }\n else:\n row[\"metadata\"] = {\n \"error\": error,\n \"processing_status\": \"failed\",\n }\n\n async def run_batch(self) -> DataFrame:\n \"\"\"Process each row in df[column_name] with the language model asynchronously.\n\n Returns:\n DataFrame: A new DataFrame containing:\n - All original columns\n - The model's response column (customizable name)\n - 'batch_index' column for processing order\n - 'metadata' (optional)\n\n Raises:\n ValueError: If the specified column is not found in the DataFrame\n TypeError: If the model is not compatible or input types are wrong\n \"\"\"\n model: Runnable = self.model\n system_msg = self.system_message or \"\"\n df: DataFrame = self.df\n col_name = self.column_name or \"\"\n\n # Validate inputs first\n if not isinstance(df, DataFrame):\n msg = f\"Expected DataFrame input, got {type(df)}\"\n raise TypeError(msg)\n\n if col_name and col_name not in df.columns:\n msg = f\"Column '{col_name}' not found in the DataFrame. Available columns: {', '.join(df.columns)}\"\n raise ValueError(msg)\n\n try:\n # Determine text input for each row\n if col_name:\n user_texts = df[col_name].astype(str).tolist()\n else:\n user_texts = [\n self._format_row_as_toml(cast(\"dict[str, Any]\", row)) for row in df.to_dict(orient=\"records\")\n ]\n\n total_rows = len(user_texts)\n await logger.ainfo(f\"Processing {total_rows} rows with batch run\")\n\n # Prepare the batch of conversations\n conversations = [\n [{\"role\": \"system\", \"content\": system_msg}, {\"role\": \"user\", \"content\": text}]\n if system_msg\n else [{\"role\": \"user\", \"content\": text}]\n for text in user_texts\n ]\n\n # Configure the model with project info and callbacks\n # Some models (e.g., ChatWatsonx) may have serialization issues with with_config()\n # due to SecretStr or other non-serializable attributes\n try:\n model = model.with_config(\n {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n )\n except (TypeError, ValueError, AttributeError) as e:\n # Log warning and continue without configuration\n await logger.awarning(\n f\"Could not configure model with callbacks and project info: {e!s}. \"\n \"Proceeding with batch processing without configuration.\"\n )\n # Process batches and track progress\n responses_with_idx = list(\n zip(\n range(len(conversations)),\n await model.abatch(list(conversations)),\n strict=True,\n )\n )\n\n # Sort by index to maintain order\n responses_with_idx.sort(key=lambda x: x[0])\n\n # Build the final data with enhanced metadata\n rows: list[dict[str, Any]] = []\n for idx, (original_row, response) in enumerate(\n zip(df.to_dict(orient=\"records\"), responses_with_idx, strict=False)\n ):\n response_text = response[1].content if hasattr(response[1], \"content\") else str(response[1])\n row = self._create_base_row(\n cast(\"dict[str, Any]\", original_row), model_response=response_text, batch_index=idx\n )\n self._add_metadata(row, success=True, system_msg=system_msg)\n rows.append(row)\n\n # Log progress\n if (idx + 1) % max(1, total_rows // 10) == 0:\n await logger.ainfo(f\"Processed {idx + 1}/{total_rows} rows\")\n\n await logger.ainfo(\"Batch processing completed successfully\")\n return DataFrame(rows)\n\n except (KeyError, AttributeError) as e:\n # Handle data structure and attribute access errors\n await logger.aerror(f\"Data processing error: {e!s}\")\n error_row = self._create_base_row(dict.fromkeys(df.columns, \"\"), model_response=\"\", batch_index=-1)\n self._add_metadata(error_row, success=False, error=str(e))\n return DataFrame([error_row])\n" + "value": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, cast\n\nimport toml # type: ignore[import-untyped]\n\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_model_classes,\n update_model_options_in_build_config,\n)\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DataFrameInput, MessageTextInput, ModelInput, MultilineInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dataframe import DataFrame\n\nif TYPE_CHECKING:\n from langchain_core.runnables import Runnable\n\n\nclass BatchRunComponent(Component):\n display_name = \"Batch Run\"\n description = \"Runs an LLM on each row of a DataFrame column. If no column is specified, all columns are used.\"\n documentation: str = \"https://docs.langflow.org/batch-run\"\n icon = \"List\"\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"Instructions\",\n info=\"Multi-line system instruction for all rows in the DataFrame.\",\n required=False,\n ),\n DataFrameInput(\n name=\"df\",\n display_name=\"DataFrame\",\n info=\"The DataFrame whose column (specified by 'column_name') we'll treat as text messages.\",\n required=True,\n ),\n MessageTextInput(\n name=\"column_name\",\n display_name=\"Column Name\",\n info=(\n \"The name of the DataFrame column to treat as text messages. \"\n \"If empty, all columns will be formatted in TOML.\"\n ),\n required=False,\n advanced=False,\n ),\n MessageTextInput(\n name=\"output_column_name\",\n display_name=\"Output Column Name\",\n info=\"Name of the column where the model's response will be stored.\",\n value=\"model_response\",\n required=False,\n advanced=True,\n ),\n BoolInput(\n name=\"enable_metadata\",\n display_name=\"Enable Metadata\",\n info=\"If True, add metadata to the output DataFrame.\",\n value=False,\n required=False,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"LLM Results\",\n name=\"batch_results\",\n method=\"run_batch\",\n info=\"A DataFrame with all original columns plus the model's response column.\",\n ),\n ]\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n\n def _format_row_as_toml(self, row: dict[str, Any]) -> str:\n \"\"\"Convert a dictionary (row) into a TOML-formatted string.\"\"\"\n formatted_dict = {str(col): {\"value\": str(val)} for col, val in row.items()}\n return toml.dumps(formatted_dict)\n\n def _create_base_row(\n self, original_row: dict[str, Any], model_response: str = \"\", batch_index: int = -1\n ) -> dict[str, Any]:\n \"\"\"Create a base row with original columns and additional metadata.\"\"\"\n row = original_row.copy()\n row[self.output_column_name] = model_response\n row[\"batch_index\"] = batch_index\n return row\n\n def _add_metadata(\n self, row: dict[str, Any], *, success: bool = True, system_msg: str = \"\", error: str | None = None\n ) -> None:\n \"\"\"Add metadata to a row if enabled.\"\"\"\n if not self.enable_metadata:\n return\n\n if success:\n row[\"metadata\"] = {\n \"has_system_message\": bool(system_msg),\n \"input_length\": len(row.get(\"text_input\", \"\")),\n \"response_length\": len(row[self.output_column_name]),\n \"processing_status\": \"success\",\n }\n else:\n row[\"metadata\"] = {\n \"error\": error,\n \"processing_status\": \"failed\",\n }\n\n async def run_batch(self) -> DataFrame:\n \"\"\"Process each row in df[column_name] with the language model asynchronously.\"\"\"\n # Check if model is already an instance (for testing) or needs to be instantiated\n if isinstance(self.model, list):\n # Extract model configuration\n model_selection = self.model[0]\n model_name = model_selection.get(\"name\")\n provider = model_selection.get(\"provider\")\n metadata = model_selection.get(\"metadata\", {})\n\n # Get model class and parameters from metadata\n model_class = get_model_classes().get(metadata.get(\"model_class\"))\n if model_class is None:\n msg = f\"No model class defined for {model_name}\"\n raise ValueError(msg)\n\n api_key_param = metadata.get(\"api_key_param\", \"api_key\")\n model_name_param = metadata.get(\"model_name_param\", \"model\")\n\n # Get API key from global variables\n from lfx.base.models.unified_models import get_api_key_for_provider\n\n api_key = get_api_key_for_provider(self.user_id, provider, self.api_key)\n\n if not api_key and provider != \"Ollama\":\n msg = f\"{provider} API key is required. Please configure it globally.\"\n raise ValueError(msg)\n\n # Instantiate the model\n kwargs = {\n model_name_param: model_name,\n api_key_param: api_key,\n }\n model: Runnable = model_class(**kwargs)\n else:\n # Model is already an instance (typically in tests)\n model = self.model\n\n system_msg = self.system_message or \"\"\n df: DataFrame = self.df\n col_name = self.column_name or \"\"\n\n # Validate inputs first\n if not isinstance(df, DataFrame):\n msg = f\"Expected DataFrame input, got {type(df)}\"\n raise TypeError(msg)\n\n if col_name and col_name not in df.columns:\n msg = f\"Column '{col_name}' not found in the DataFrame. Available columns: {', '.join(df.columns)}\"\n raise ValueError(msg)\n\n try:\n # Determine text input for each row\n if col_name:\n user_texts = df[col_name].astype(str).tolist()\n else:\n user_texts = [\n self._format_row_as_toml(cast(\"dict[str, Any]\", row)) for row in df.to_dict(orient=\"records\")\n ]\n\n total_rows = len(user_texts)\n await logger.ainfo(f\"Processing {total_rows} rows with batch run\")\n\n # Prepare the batch of conversations\n conversations = [\n [{\"role\": \"system\", \"content\": system_msg}, {\"role\": \"user\", \"content\": text}]\n if system_msg\n else [{\"role\": \"user\", \"content\": text}]\n for text in user_texts\n ]\n\n # Configure the model with project info and callbacks\n # Some models (e.g., ChatWatsonx) may have serialization issues with with_config()\n # due to SecretStr or other non-serializable attributes\n try:\n model = model.with_config(\n {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n )\n except (TypeError, ValueError, AttributeError) as e:\n # Log warning and continue without configuration\n await logger.awarning(\n f\"Could not configure model with callbacks and project info: {e!s}. \"\n \"Proceeding with batch processing without configuration.\"\n )\n # Process batches and track progress\n responses_with_idx = list(\n zip(\n range(len(conversations)),\n await model.abatch(list(conversations)),\n strict=True,\n )\n )\n\n # Sort by index to maintain order\n responses_with_idx.sort(key=lambda x: x[0])\n\n # Build the final data with enhanced metadata\n rows: list[dict[str, Any]] = []\n for idx, (original_row, response) in enumerate(\n zip(df.to_dict(orient=\"records\"), responses_with_idx, strict=False)\n ):\n response_text = response[1].content if hasattr(response[1], \"content\") else str(response[1])\n row = self._create_base_row(\n cast(\"dict[str, Any]\", original_row), model_response=response_text, batch_index=idx\n )\n self._add_metadata(row, success=True, system_msg=system_msg)\n rows.append(row)\n\n # Log progress\n if (idx + 1) % max(1, total_rows // 10) == 0:\n await logger.ainfo(f\"Processed {idx + 1}/{total_rows} rows\")\n\n await logger.ainfo(\"Batch processing completed successfully\")\n return DataFrame(rows)\n\n except (KeyError, AttributeError) as e:\n # Handle data structure and attribute access errors\n await logger.aerror(f\"Data processing error: {e!s}\")\n error_row = self._create_base_row(dict.fromkeys(df.columns, \"\"), model_response=\"\", batch_index=-1)\n self._add_metadata(error_row, success=False, error=str(e))\n return DataFrame([error_row])\n" }, "column_name": { "_input_type": "StrInput", @@ -409,7 +429,7 @@ "advanced": false, "display_name": "Language Model", "dynamic": false, - "info": "Connect the 'Language Model' output from your LLM component here.", + "info": "Select your model provider", "input_types": [ "LanguageModel" ], @@ -759,13 +779,9 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "d64b11c24a1c", + "code_hash": "1834a4d901fa", "dependencies": { "dependencies": [ - { - "name": "langchain_core", - "version": "0.3.80" - }, { "name": "pydantic", "version": "2.11.10" @@ -773,6 +789,10 @@ { "name": "lfx", "version": null + }, + { + "name": "langchain_core", + "version": "0.3.80" } ], "total_dependencies": 3 @@ -843,71 +863,12 @@ "type": "str", "value": "A helpful assistant with access to the following tools:" }, - "agent_llm": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "dialog_inputs": {}, - "display_name": "Model Provider", - "dynamic": false, - "external_options": { - "fields": { - "data": { - "node": { - "display_name": "Connect other models", - "icon": "CornerDownLeft", - "name": "connect_other_models" - } - } - } - }, - "info": "The provider of the language model that the agent will use to generate responses.", - "input_types": [], - "name": "agent_llm", - "options": [ - "Anthropic", - "Google Generative AI", - "OpenAI", - "IBM watsonx.ai", - "Ollama" - ], - "options_metadata": [ - { - "icon": "Anthropic" - }, - { - "icon": "GoogleGenerativeAI" - }, - { - "icon": "OpenAI" - }, - { - "icon": "WatsonxAI" - }, - { - "icon": "Ollama" - } - ], - "override_skip": false, - "placeholder": "", - "real_time_refresh": true, - "refresh_button": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "OpenAI" - }, "api_key": { "_input_type": "SecretStrInput", "advanced": false, - "display_name": "OpenAI API Key", + "display_name": "API Key", "dynamic": false, - "info": "The OpenAI API Key to use for the OpenAI model.", + "info": "Model Provider API key", "input_types": [], "load_from_db": true, "name": "api_key", @@ -920,27 +881,6 @@ "type": "str", "value": "OPENAI_API_KEY" }, - "base_url": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Base URL", - "dynamic": false, - "info": "The base URL of the API.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "base_url", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "code": { "advanced": true, "dynamic": true, @@ -957,7 +897,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import json\nimport re\n\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import ValidationError\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODEL_PROVIDERS_LIST,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, SecretStrInput, StrInput\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n }\n }\n },\n },\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"The API key to use for the model.\",\n required=True,\n ),\n StrInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"The base URL of the API.\",\n required=True,\n show=False,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"Project ID\",\n info=\"The project ID of the model.\",\n required=True,\n show=False,\n ),\n IntInput(\n name=\"max_output_tokens\",\n display_name=\"Max Output Tokens\",\n info=\"The maximum number of tokens to generate.\",\n show=False,\n ),\n *openai_inputs_filtered,\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n # Filter out json_mode and only use attributes that exist on this component\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n if build_config is not None and field in build_config:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n build_config[\"agent_llm\"][\"display_name\"] = \"Model Provider\"\n elif field_value == \"connect_other_models\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n real_time_refresh=True,\n refresh_button=False,\n input_types=[\"LanguageModel\"],\n placeholder=\"Awaiting model input.\",\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n external_options={\n \"fields\": {\n \"data\": {\n \"node\": {\n \"name\": \"connect_other_models\",\n \"display_name\": \"Connect other models\",\n \"icon\": \"CornerDownLeft\",\n },\n }\n },\n },\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" + "value": "from __future__ import annotations\n\nimport json\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom pydantic import ValidationError\n\nfrom lfx.components.models_and_agents.memory import MemoryComponent\n\nif TYPE_CHECKING:\n from langchain_core.tools import Tool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.agents.events import ExceptionWithMessageError\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.custom.custom_component.component import get_component_toolkit\nfrom lfx.helpers.base_model import build_model_from_schema\nfrom lfx.inputs.inputs import BoolInput, ModelInput\nfrom lfx.io import IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.schema.table import EditMode\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n documentation: str = \"https://docs.langflow.org/agents\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n real_time_refresh=True,\n advanced=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n MultilineInput(\n name=\"format_instructions\",\n display_name=\"Output Format Instructions\",\n info=\"Generic Template for structured output formatting. Valid only with Structured response.\",\n value=(\n \"You are an AI that extracts structured JSON objects from unstructured text. \"\n \"Use a predefined schema with expected types (str, int, float, bool, dict). \"\n \"Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. \"\n \"Fill missing or ambiguous values with defaults: null for missing values. \"\n \"Remove exact duplicates but keep variations that have different field values. \"\n \"Always return valid JSON in the expected format, never throw errors. \"\n \"If multiple objects can be extracted, return them all in the structured format.\"\n ),\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=(\n \"Schema Validation: Define the structure and data types for structured output. \"\n \"No validation if no output schema.\"\n ),\n advanced=True,\n required=False,\n value=[],\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate the data type of the output field (e.g., str, int, float, bool, dict).\"),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"As List\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n # removed memory inputs from agent component\n # *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the agent.\"\"\"\n from langchain_core.tools import StructuredTool\n\n llm_model = get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n )\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n await logger.adebug(f\"Retrieved {len(self.chat_history)} chat history messages\")\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Set shared callbacks for tracing the tools used by the agent\n self.set_tools_callbacks(self.tools, self._get_shared_callbacks())\n\n return llm_model, self.chat_history, self.tools\n\n async def message_response(self) -> Message:\n try:\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n result = await self.run_agent(agent)\n\n # Store result for potential JSON output\n self._agent_result = result\n\n except (ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n await logger.aerror(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n # Avoid catching blind Exception; let truly unexpected exceptions propagate\n except Exception as e:\n await logger.aerror(f\"Unexpected error: {e!s}\")\n raise\n else:\n return result\n\n def _preprocess_schema(self, schema):\n \"\"\"Preprocess schema to ensure correct data types for build_model_from_schema.\"\"\"\n processed_schema = []\n for field in schema:\n processed_field = {\n \"name\": str(field.get(\"name\", \"field\")),\n \"type\": str(field.get(\"type\", \"str\")),\n \"description\": str(field.get(\"description\", \"\")),\n \"multiple\": field.get(\"multiple\", False),\n }\n # Ensure multiple is handled correctly\n if isinstance(processed_field[\"multiple\"], str):\n processed_field[\"multiple\"] = processed_field[\"multiple\"].lower() in [\n \"true\",\n \"1\",\n \"t\",\n \"y\",\n \"yes\",\n ]\n processed_schema.append(processed_field)\n return processed_schema\n\n async def build_structured_output_base(self, content: str):\n \"\"\"Build structured output with optional BaseModel validation.\"\"\"\n json_pattern = r\"\\{.*\\}\"\n schema_error_msg = \"Try setting an output schema\"\n\n # Try to parse content as JSON first\n json_data = None\n try:\n json_data = json.loads(content)\n except json.JSONDecodeError:\n json_match = re.search(json_pattern, content, re.DOTALL)\n if json_match:\n try:\n json_data = json.loads(json_match.group())\n except json.JSONDecodeError:\n return {\"content\": content, \"error\": schema_error_msg}\n else:\n return {\"content\": content, \"error\": schema_error_msg}\n\n # If no output schema provided, return parsed JSON without validation\n if not hasattr(self, \"output_schema\") or not self.output_schema or len(self.output_schema) == 0:\n return json_data\n\n # Use BaseModel validation with schema\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n\n # Validate against the schema\n if isinstance(json_data, list):\n # Multiple objects\n validated_objects = []\n for item in json_data:\n try:\n validated_obj = output_model.model_validate(item)\n validated_objects.append(validated_obj.model_dump())\n except ValidationError as e:\n await logger.aerror(f\"Validation error for item: {e}\")\n # Include invalid items with error info\n validated_objects.append({\"data\": item, \"validation_error\": str(e)})\n return validated_objects\n\n # Single object\n try:\n validated_obj = output_model.model_validate(json_data)\n return [validated_obj.model_dump()] # Return as list for consistency\n except ValidationError as e:\n await logger.aerror(f\"Validation error: {e}\")\n return [{\"data\": json_data, \"validation_error\": str(e)}]\n\n except (TypeError, ValueError) as e:\n await logger.aerror(f\"Error building structured output: {e}\")\n # Fallback to parsed JSON without validation\n return json_data\n\n async def json_response(self) -> Data:\n \"\"\"Convert agent response to structured JSON Data output with schema validation.\"\"\"\n # Always use structured chat agent for JSON response mode for better JSON formatting\n try:\n system_components = []\n\n # 1. Agent Instructions (system_prompt)\n agent_instructions = getattr(self, \"system_prompt\", \"\") or \"\"\n if agent_instructions:\n system_components.append(f\"{agent_instructions}\")\n\n # 2. Format Instructions\n format_instructions = getattr(self, \"format_instructions\", \"\") or \"\"\n if format_instructions:\n system_components.append(f\"Format instructions: {format_instructions}\")\n\n # 3. Schema Information from BaseModel\n if hasattr(self, \"output_schema\") and self.output_schema and len(self.output_schema) > 0:\n try:\n processed_schema = self._preprocess_schema(self.output_schema)\n output_model = build_model_from_schema(processed_schema)\n schema_dict = output_model.model_json_schema()\n schema_info = (\n \"You are given some text that may include format instructions, \"\n \"explanations, or other content alongside a JSON schema.\\n\\n\"\n \"Your task:\\n\"\n \"- Extract only the JSON schema.\\n\"\n \"- Return it as valid JSON.\\n\"\n \"- Do not include format instructions, explanations, or extra text.\\n\\n\"\n \"Input:\\n\"\n f\"{json.dumps(schema_dict, indent=2)}\\n\\n\"\n \"Output (only JSON schema):\"\n )\n system_components.append(schema_info)\n except (ValidationError, ValueError, TypeError, KeyError) as e:\n await logger.aerror(f\"Could not build schema for prompt: {e}\", exc_info=True)\n\n # Combine all components\n combined_instructions = \"\\n\\n\".join(system_components) if system_components else \"\"\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n self.set(\n llm=llm_model,\n tools=self.tools or [],\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=combined_instructions,\n )\n\n # Create and run structured chat agent\n try:\n structured_agent = self.create_agent_runnable()\n except (NotImplementedError, ValueError, TypeError) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n raise\n try:\n result = await self.run_agent(structured_agent)\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n RuntimeError,\n ) as e:\n await logger.aerror(f\"Error with structured agent result: {e}\")\n raise\n # Extract content from structured agent result\n if hasattr(result, \"content\"):\n content = result.content\n elif hasattr(result, \"text\"):\n content = result.text\n else:\n content = str(result)\n\n except (\n ExceptionWithMessageError,\n ValueError,\n TypeError,\n NotImplementedError,\n AttributeError,\n ) as e:\n await logger.aerror(f\"Error with structured chat agent: {e}\")\n # Fallback to regular agent\n content_str = \"No content returned from agent\"\n return Data(data={\"content\": content_str, \"error\": str(e)})\n\n # Process with structured output validation\n try:\n structured_output = await self.build_structured_output_base(content)\n\n # Handle different output formats\n if isinstance(structured_output, list) and structured_output:\n if len(structured_output) == 1:\n return Data(data=structured_output[0])\n return Data(data={\"results\": structured_output})\n if isinstance(structured_output, dict):\n return Data(data=structured_output)\n return Data(data={\"content\": content})\n\n except (ValueError, TypeError) as e:\n await logger.aerror(f\"Error in structured output processing: {e}\")\n return Data(data={\"content\": content, \"error\": str(e)})\n\n async def get_memory_data(self):\n # TODO: This is a temporary fix to avoid message duplication. We should develop a function for this.\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(\n session_id=self.graph.session_id,\n context_id=self.context_id,\n order=\"Ascending\",\n n_messages=self.n_messages,\n )\n .retrieve_messages()\n )\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self,\n build_config: dotdict,\n field_value: list[dict],\n field_name: str | None = None,\n ) -> dotdict:\n # Update model options with caching (for all field changes)\n # Agents require tool calling, so filter for only tool-calling capable models\n def get_tool_calling_model_options(user_id=None):\n return get_language_model_options(user_id=user_id, tool_calling=True)\n\n build_config = update_model_options_in_build_config(\n component=self,\n build_config=dict(build_config),\n cache_key_prefix=\"language_model_options_tool_calling\",\n get_options_func=get_tool_calling_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n build_config = dotdict(build_config)\n\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n if field_name == \"model\":\n self.log(str(field_value))\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"model\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n component_toolkit = get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_Agent\",\n tool_description=description,\n # here we do not use the shared callbacks as we are exposing the agent as a tool\n callbacks=self.get_langchain_callbacks(),\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n\n return tools\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -1066,137 +1006,42 @@ "type": "int", "value": 15 }, - "max_output_tokens": { - "_input_type": "IntInput", + "model": { + "_input_type": "ModelInput", "advanced": false, - "display_name": "Max Output Tokens", - "dynamic": false, - "info": "The maximum number of tokens to generate.", - "list": false, - "list_add_label": "Add More", - "name": "max_output_tokens", - "override_skip": false, - "placeholder": "", - "required": false, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "max_retries": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Retries", - "dynamic": false, - "info": "The maximum number of retries to make when generating.", - "list": false, - "list_add_label": "Add More", - "name": "max_retries", - "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": 5 - }, - "max_tokens": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Max Tokens", + "display_name": "Language Model", "dynamic": false, - "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", - "list": false, - "list_add_label": "Add More", - "name": "max_tokens", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 128000.0, - "min": 0.0, - "step": 0.1, - "step_type": "float" + "external_options": { + "fields": { + "data": { + "node": { + "display_name": "Connect other models", + "icon": "CornerDownLeft", + "name": "connect_other_models" + } + } + } }, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "int", - "value": "" - }, - "model_kwargs": { - "_input_type": "DictInput", - "advanced": true, - "display_name": "Model Kwargs", - "dynamic": false, - "info": "Additional keyword arguments to pass to the model.", + "info": "Select your model provider", + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", - "name": "model_kwargs", + "model_type": "language", + "name": "model", "override_skip": false, - "placeholder": "", - "required": false, + "placeholder": "Setup Provider", + "real_time_refresh": true, + "refresh_button": true, + "required": true, "show": true, "title_case": false, "tool_mode": false, "trace_as_input": true, "track_in_telemetry": false, - "type": "dict", - "value": {} - }, - "model_name": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": true, - "dialog_inputs": {}, - "display_name": "Model Name", - "dynamic": false, - "external_options": {}, - "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.", - "name": "model_name", - "options": [ - "gpt-4o-mini", - "gpt-4o", - "gpt-4.1", - "gpt-4.1-mini", - "gpt-4.1-nano", - "gpt-4-turbo", - "gpt-4-turbo-preview", - "gpt-4", - "gpt-3.5-turbo", - "gpt-5.1", - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", - "gpt-5-chat-latest", - "o1", - "o3-mini", - "o3", - "o3-pro", - "o4-mini", - "o4-mini-high" - ], - "options_metadata": [], - "override_skip": false, - "placeholder": "", - "real_time_refresh": false, - "required": false, - "show": true, - "title_case": false, - "toggle": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": true, - "type": "str", - "value": "gpt-4o-mini" + "type": "model", + "value": "" }, "n_messages": { "_input_type": "IntInput", @@ -1216,27 +1061,6 @@ "type": "int", "value": 100 }, - "openai_api_base": { - "_input_type": "StrInput", - "advanced": true, - "display_name": "OpenAI API Base", - "dynamic": false, - "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "openai_api_base", - "override_skip": false, - "placeholder": "", - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, "output_schema": { "_input_type": "TableInput", "advanced": true, @@ -1299,47 +1123,6 @@ "type": "table", "value": [] }, - "project_id": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Project ID", - "dynamic": false, - "info": "The project ID of the model.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "project_id", - "override_skip": false, - "placeholder": "", - "required": true, - "show": false, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "track_in_telemetry": false, - "type": "str", - "value": "" - }, - "seed": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Seed", - "dynamic": false, - "info": "The seed controls the reproducibility of the job.", - "list": false, - "list_add_label": "Add More", - "name": "seed", - "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": 1 - }, "system_prompt": { "_input_type": "MultilineInput", "advanced": false, @@ -1365,56 +1148,6 @@ "type": "str", "value": "You are a specialized assistant focused on comprehensive YouTube video analysis. Your main responsibilities are:\n\n1. Extract video transcripts using YouTubeTranscripts-get_message_output tool\n2. Process sentiment analysis from YouTube comments provided in XML format\n3. Create comprehensive analysis by combining:\n - Video content (from transcript)\n - Audience reception (from comment sentiment analysis)\n\nYour analysis should:\n- Identify main themes and topics from the video transcript\n- Evaluate audience sentiment patterns from provided comments\n- Highlight any disconnect between video content and audience reception\n- Provide actionable insights based on both content and reception\n\nInput received:\n- Video transcript (obtained through tool)\n- Sentiment analysis of comments in XML format containing:\n \n : Detailed analysis of comment\n : Positive/Neutral/Negative\n \n\nOutput format:\n1. Content Summary: Key points from transcript\n2. Audience Reception: Pattern analysis from sentiment data\n3. Synthesis: Combined analysis of content and reception\n4. Recommendations: Based on analysis" }, - "temperature": { - "_input_type": "SliderInput", - "advanced": true, - "display_name": "Temperature", - "dynamic": false, - "info": "", - "max_label": "", - "max_label_icon": "", - "min_label": "", - "min_label_icon": "", - "name": "temperature", - "override_skip": false, - "placeholder": "", - "range_spec": { - "max": 1.0, - "min": 0.0, - "step": 0.01, - "step_type": "float" - }, - "required": false, - "show": true, - "slider_buttons": false, - "slider_buttons_options": [], - "slider_input": false, - "title_case": false, - "tool_mode": false, - "track_in_telemetry": false, - "type": "slider", - "value": 0.1 - }, - "timeout": { - "_input_type": "IntInput", - "advanced": true, - "display_name": "Timeout", - "dynamic": false, - "info": "The timeout for requests to OpenAI completion API.", - "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": 700 - }, "tools": { "_input_type": "HandleInput", "advanced": false, @@ -1683,7 +1416,7 @@ "legacy": false, "lf_version": "1.4.3", "metadata": { - "code_hash": "cae45e2d53f6", + "code_hash": "8c87e536cca4", "dependencies": { "dependencies": [ { @@ -1759,7 +1492,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id or self.graph.session_id or \"\"\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" + "value": "from collections.abc import Generator\nfrom typing import Any\n\nimport orjson\nfrom fastapi.encoders import jsonable_encoder\n\nfrom lfx.base.io.chat import ChatComponent\nfrom lfx.helpers.data import safe_convert\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.schema.properties import Source\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n documentation: str = \"https://docs.langflow.org/chat-input-and-output\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Inputs\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"context_id\",\n display_name=\"Context ID\",\n info=\"The context ID of the chat. Adds an extra layer to the local memory.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n advanced=True,\n info=\"Whether to clean data before converting to string.\",\n ),\n ]\n outputs = [\n Output(\n display_name=\"Output Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n\n # Get source properties\n source, _, display_name, source_id = self.get_properties_from_source_component()\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():\n message = self.input_value\n # Update message properties\n message.text = text\n # Preserve existing session_id from the incoming message if it exists\n existing_session_id = message.session_id\n else:\n message = Message(text=text)\n existing_session_id = None\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n # Preserve session_id from incoming message, or use component/graph session_id\n message.session_id = (\n self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, \"graph\") else None) or \"\"\n )\n message.context_id = self.context_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n\n # Store message if needed\n if message.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _serialize_data(self, data: Data) -> str:\n \"\"\"Serialize Data object to JSON string.\"\"\"\n # Convert data.data to JSON-serializable format\n serializable_data = jsonable_encoder(data.data)\n # Serialize with orjson, enabling pretty printing with indentation\n json_bytes = orjson.dumps(serializable_data, option=orjson.OPT_INDENT_2)\n # Convert bytes to string and wrap in Markdown code blocks\n return \"```json\\n\" + json_bytes.decode(\"utf-8\") + \"\\n```\"\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n clean_data: bool = getattr(self, \"clean_data\", False)\n return \"\\n\".join([safe_convert(item, clean_data=clean_data) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return safe_convert(self.input_value)\n" }, "context_id": { "_input_type": "MessageTextInput", @@ -2539,7 +2272,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from typing import Any\n\nimport requests\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_ibm import ChatWatsonx\nfrom langchain_ollama import ChatOllama\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom lfx.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS\nfrom lfx.base.models.google_generative_ai_model import ChatGoogleGenerativeAIFixed\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.model_utils import get_ollama_models, is_valid_ollama_url\nfrom lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, MessageTextInput, StrInput\nfrom lfx.io import DropdownInput, MessageInput, MultilineInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.util import transform_localhost_url\n\n# IBM watsonx.ai constants\nIBM_WATSONX_DEFAULT_MODELS = [\"ibm/granite-3-2b-instruct\", \"ibm/granite-3-8b-instruct\", \"ibm/granite-13b-instruct-v2\"]\nIBM_WATSONX_URLS = [\n \"https://us-south.ml.cloud.ibm.com\",\n \"https://eu-de.ml.cloud.ibm.com\",\n \"https://eu-gb.ml.cloud.ibm.com\",\n \"https://au-syd.ml.cloud.ibm.com\",\n \"https://jp-tok.ml.cloud.ibm.com\",\n \"https://ca-tor.ml.cloud.ibm.com\",\n]\n\n# Ollama API constants\nHTTP_STATUS_OK = 200\nJSON_MODELS_KEY = \"models\"\nJSON_NAME_KEY = \"name\"\nJSON_CAPABILITIES_KEY = \"capabilities\"\nDESIRED_CAPABILITY = \"completion\"\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n @staticmethod\n def fetch_ibm_models(base_url: str) -> list[str]:\n \"\"\"Fetch available models from the watsonx.ai API.\"\"\"\n try:\n endpoint = f\"{base_url}/ml/v1/foundation_model_specs\"\n params = {\"version\": \"2024-09-16\", \"filters\": \"function_text_chat,!lifecycle_withdrawn\"}\n response = requests.get(endpoint, params=params, timeout=10)\n response.raise_for_status()\n data = response.json()\n models = [model[\"model_id\"] for model in data.get(\"resources\", [])]\n return sorted(models)\n except Exception: # noqa: BLE001\n logger.exception(\"Error fetching IBM watsonx models. Using default models.\")\n return IBM_WATSONX_DEFAULT_MODELS\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Model Provider\",\n options=[\"OpenAI\", \"Anthropic\", \"Google\", \"IBM watsonx.ai\", \"Ollama\"],\n value=\"OpenAI\",\n info=\"Select the model provider\",\n real_time_refresh=True,\n options_metadata=[\n {\"icon\": \"OpenAI\"},\n {\"icon\": \"Anthropic\"},\n {\"icon\": \"GoogleGenerativeAI\"},\n {\"icon\": \"WatsonxAI\"},\n {\"icon\": \"Ollama\"},\n ],\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,\n value=OPENAI_CHAT_MODEL_NAMES[0],\n info=\"Select the model to use\",\n real_time_refresh=True,\n refresh_button=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageTextInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n provider = self.provider\n model_name = self.model_name\n temperature = self.temperature\n stream = self.stream\n\n if provider == \"OpenAI\":\n if not self.api_key:\n msg = \"OpenAI API key is required when using OpenAI provider\"\n raise ValueError(msg)\n\n if model_name in OPENAI_REASONING_MODEL_NAMES:\n # reasoning models do not support temperature (yet)\n temperature = None\n\n return ChatOpenAI(\n model_name=model_name,\n temperature=temperature,\n streaming=stream,\n openai_api_key=self.api_key,\n )\n if provider == \"Anthropic\":\n if not self.api_key:\n msg = \"Anthropic API key is required when using Anthropic provider\"\n raise ValueError(msg)\n return ChatAnthropic(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n anthropic_api_key=self.api_key,\n )\n if provider == \"Google\":\n if not self.api_key:\n msg = \"Google API key is required when using Google provider\"\n raise ValueError(msg)\n return ChatGoogleGenerativeAIFixed(\n model=model_name,\n temperature=temperature,\n streaming=stream,\n google_api_key=self.api_key,\n )\n if provider == \"IBM watsonx.ai\":\n if not self.api_key:\n msg = \"IBM API key is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.base_url_ibm_watsonx:\n msg = \"IBM watsonx API Endpoint is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n if not self.project_id:\n msg = \"IBM watsonx Project ID is required when using IBM watsonx.ai provider\"\n raise ValueError(msg)\n return ChatWatsonx(\n apikey=SecretStr(self.api_key).get_secret_value(),\n url=self.base_url_ibm_watsonx,\n project_id=self.project_id,\n model_id=model_name,\n params={\n \"temperature\": temperature,\n },\n streaming=stream,\n )\n if provider == \"Ollama\":\n if not self.ollama_base_url:\n msg = \"Ollama API URL is required when using Ollama provider\"\n raise ValueError(msg)\n if not model_name:\n msg = \"Model name is required when using Ollama provider\"\n raise ValueError(msg)\n\n transformed_base_url = transform_localhost_url(self.ollama_base_url)\n\n # Check if URL contains /v1 suffix (OpenAI-compatible mode)\n if transformed_base_url and transformed_base_url.rstrip(\"/\").endswith(\"/v1\"):\n # Strip /v1 suffix and log warning\n transformed_base_url = transformed_base_url.rstrip(\"/\").removesuffix(\"/v1\")\n logger.warning(\n \"Detected '/v1' suffix in base URL. The Ollama component uses the native Ollama API, \"\n \"not the OpenAI-compatible API. The '/v1' suffix has been automatically removed. \"\n \"If you want to use the OpenAI-compatible API, please use the OpenAI component instead. \"\n \"Learn more at https://docs.ollama.com/openai#openai-compatibility\"\n )\n\n return ChatOllama(\n base_url=transformed_base_url,\n model=model_name,\n temperature=temperature,\n )\n msg = f\"Unknown provider: {provider}\"\n raise ValueError(msg)\n\n async def update_build_config(\n self, build_config: dotdict, field_value: Any, field_name: str | None = None\n ) -> dotdict:\n if field_name == \"provider\":\n if field_value == \"OpenAI\":\n build_config[\"model_name\"][\"options\"] = OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES\n build_config[\"model_name\"][\"value\"] = OPENAI_CHAT_MODEL_NAMES[0]\n build_config[\"api_key\"][\"display_name\"] = \"OpenAI API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Anthropic\":\n build_config[\"model_name\"][\"options\"] = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"value\"] = ANTHROPIC_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Anthropic API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Google\":\n build_config[\"model_name\"][\"options\"] = GOOGLE_GENERATIVE_AI_MODELS\n build_config[\"model_name\"][\"value\"] = GOOGLE_GENERATIVE_AI_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"Google API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"IBM watsonx.ai\":\n build_config[\"model_name\"][\"options\"] = IBM_WATSONX_DEFAULT_MODELS\n build_config[\"model_name\"][\"value\"] = IBM_WATSONX_DEFAULT_MODELS[0]\n build_config[\"api_key\"][\"display_name\"] = \"IBM API Key\"\n build_config[\"api_key\"][\"show\"] = True\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = True\n build_config[\"project_id\"][\"show\"] = True\n build_config[\"ollama_base_url\"][\"show\"] = False\n elif field_value == \"Ollama\":\n # Fetch Ollama models from the API\n build_config[\"api_key\"][\"show\"] = False\n build_config[\"base_url_ibm_watsonx\"][\"show\"] = False\n build_config[\"project_id\"][\"show\"] = False\n build_config[\"ollama_base_url\"][\"show\"] = True\n\n # Try multiple sources to get the URL (in order of preference):\n # 1. Instance attribute (already resolved from global/db)\n # 2. Build config value (may be a global variable reference)\n # 3. Default value\n ollama_url = getattr(self, \"ollama_base_url\", None)\n if not ollama_url:\n config_value = build_config[\"ollama_base_url\"].get(\"value\", DEFAULT_OLLAMA_URL)\n # If config_value looks like a variable name (all caps with underscores), use default\n is_variable_ref = (\n config_value\n and isinstance(config_value, str)\n and config_value.isupper()\n and \"_\" in config_value\n )\n if is_variable_ref:\n await logger.adebug(\n f\"Config value appears to be a variable reference: {config_value}, using default\"\n )\n ollama_url = DEFAULT_OLLAMA_URL\n else:\n ollama_url = config_value\n\n await logger.adebug(f\"Fetching Ollama models for provider switch. URL: {ollama_url}\")\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n except ValueError:\n await logger.awarning(\"Failed to fetch Ollama models. Setting empty options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {ollama_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif (\n field_name == \"base_url_ibm_watsonx\"\n and field_value\n and hasattr(self, \"provider\")\n and self.provider == \"IBM watsonx.ai\"\n ):\n # Fetch IBM models when base_url changes\n try:\n models = self.fetch_ibm_models(base_url=field_value)\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else IBM_WATSONX_DEFAULT_MODELS[0]\n info_message = f\"Updated model options: {len(models)} models found in {field_value}\"\n logger.info(info_message)\n except Exception: # noqa: BLE001\n logger.exception(\"Error updating IBM model options.\")\n elif field_name == \"ollama_base_url\":\n # Fetch Ollama models when ollama_base_url changes\n # Use the field_value directly since this is triggered when the field changes\n logger.debug(\n f\"Fetching Ollama models from updated URL: {build_config['ollama_base_url']} \\\n and value {self.ollama_base_url}\",\n )\n await logger.adebug(f\"Fetching Ollama models from updated URL: {self.ollama_base_url}\")\n if await is_valid_ollama_url(url=self.ollama_base_url):\n try:\n models = await get_ollama_models(\n base_url_value=self.ollama_base_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n build_config[\"model_name\"][\"value\"] = models[0] if models else \"\"\n info_message = f\"Updated model options: {len(models)} models found in {self.ollama_base_url}\"\n await logger.ainfo(info_message)\n except ValueError:\n await logger.awarning(\"Error updating Ollama model options.\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n else:\n await logger.awarning(f\"Invalid Ollama URL: {self.ollama_base_url}\")\n build_config[\"model_name\"][\"options\"] = []\n build_config[\"model_name\"][\"value\"] = \"\"\n elif field_name == \"model_name\":\n # Refresh Ollama models when model_name field is accessed\n if hasattr(self, \"provider\") and self.provider == \"Ollama\":\n ollama_url = getattr(self, \"ollama_base_url\", DEFAULT_OLLAMA_URL)\n if await is_valid_ollama_url(url=ollama_url):\n try:\n models = await get_ollama_models(\n base_url_value=ollama_url,\n desired_capability=DESIRED_CAPABILITY,\n json_models_key=JSON_MODELS_KEY,\n json_name_key=JSON_NAME_KEY,\n json_capabilities_key=JSON_CAPABILITIES_KEY,\n )\n build_config[\"model_name\"][\"options\"] = models\n except ValueError:\n await logger.awarning(\"Failed to refresh Ollama models.\")\n build_config[\"model_name\"][\"options\"] = []\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n # Hide system_message for o1 models - currently unsupported\n if field_value and field_value.startswith(\"o1\") and hasattr(self, \"provider\") and self.provider == \"OpenAI\":\n if \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = False\n elif \"system_message\" in build_config:\n build_config[\"system_message\"][\"show\"] = True\n return build_config\n" + "value": "from lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.unified_models import (\n get_language_model_options,\n get_llm,\n update_model_options_in_build_config,\n)\nfrom lfx.base.models.watsonx_constants import IBM_WATSONX_URLS\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, StrInput\nfrom lfx.io import MessageInput, ModelInput, MultilineInput, SecretStrInput, SliderInput\n\nDEFAULT_OLLAMA_URL = \"http://localhost:11434\"\n\n\nclass LanguageModelComponent(LCModelComponent):\n display_name = \"Language Model\"\n description = \"Runs a language model given a specified provider.\"\n documentation: str = \"https://docs.langflow.org/components-models\"\n icon = \"brain-circuit\"\n category = \"models\"\n priority = 0 # Set priority to 0 to make it appear first\n\n inputs = [\n ModelInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Select your model provider\",\n real_time_refresh=True,\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"API Key\",\n info=\"Model Provider API key\",\n required=False,\n show=True,\n real_time_refresh=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"base_url_ibm_watsonx\",\n display_name=\"watsonx API Endpoint\",\n info=\"The base URL of the API (IBM watsonx.ai only)\",\n options=IBM_WATSONX_URLS,\n value=IBM_WATSONX_URLS[0],\n show=False,\n real_time_refresh=True,\n ),\n StrInput(\n name=\"project_id\",\n display_name=\"watsonx Project ID\",\n info=\"The project ID associated with the foundation model (IBM watsonx.ai only)\",\n show=False,\n required=False,\n ),\n MessageInput(\n name=\"ollama_base_url\",\n display_name=\"Ollama API URL\",\n info=f\"Endpoint of the Ollama API (Ollama only). Defaults to {DEFAULT_OLLAMA_URL}\",\n value=DEFAULT_OLLAMA_URL,\n show=False,\n real_time_refresh=True,\n load_from_db=True,\n ),\n MessageInput(\n name=\"input_value\",\n display_name=\"Input\",\n info=\"The input text to send to the model\",\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"A system message that helps set the behavior of the assistant\",\n advanced=False,\n ),\n BoolInput(\n name=\"stream\",\n display_name=\"Stream\",\n info=\"Whether to stream the response\",\n value=False,\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Controls randomness in responses\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n return get_llm(\n model=self.model,\n user_id=self.user_id,\n api_key=self.api_key,\n temperature=self.temperature,\n stream=self.stream,\n )\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n \"\"\"Dynamically update build config with user-filtered model options.\"\"\"\n return update_model_options_in_build_config(\n component=self,\n build_config=build_config,\n cache_key_prefix=\"language_model_options\",\n get_options_func=get_language_model_options,\n field_name=field_name,\n field_value=field_value,\n )\n" }, "input_value": { "_input_type": "MessageInput", From 76d8ea180c8f28098e614179ef2e3ee4eb1454a8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:47:47 +0000 Subject: [PATCH 4/8] [autofix.ci] apply automated fixes (attempt 3/3) --- src/lfx/src/lfx/_assets/component_index.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lfx/src/lfx/_assets/component_index.json b/src/lfx/src/lfx/_assets/component_index.json index 66f382970917..de86190ca944 100644 --- a/src/lfx/src/lfx/_assets/component_index.json +++ b/src/lfx/src/lfx/_assets/component_index.json @@ -1 +1 @@ -{"entries":[["FAISS",{"FAISS":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"FAISS Vector Store with search capabilities","display_name":"FAISS","documentation":"","edited":false,"field_order":["index_name","persist_directory","ingest_data","search_query","should_cache_vector_store","allow_dangerous_deserialization","embedding","number_of_results"],"frozen":false,"icon":"FAISS","legacy":false,"metadata":{"code_hash":"2bd7a064d724","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.FAISS.faiss.FaissVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","allow_dangerous_deserialization":{"_input_type":"BoolInput","advanced":true,"display_name":"Allow Dangerous Deserialization","dynamic":false,"info":"Set to True to allow loading pickle files from untrusted sources. Only enable this if you trust the source of the data.","list":false,"list_add_label":"Add More","name":"allow_dangerous_deserialization","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from pathlib import Path\n\nfrom langchain_community.vectorstores import FAISS\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.io import BoolInput, HandleInput, IntInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass FaissVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"FAISS Vector Store with search capabilities.\"\"\"\n\n display_name: str = \"FAISS\"\n description: str = \"FAISS Vector Store with search capabilities\"\n name = \"FAISS\"\n icon = \"FAISS\"\n\n inputs = [\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow_index\",\n ),\n StrInput(\n name=\"persist_directory\",\n display_name=\"Persist Directory\",\n info=\"Path to save the FAISS index. It will be relative to where Langflow is running.\",\n ),\n *LCVectorStoreComponent.inputs,\n BoolInput(\n name=\"allow_dangerous_deserialization\",\n display_name=\"Allow Dangerous Deserialization\",\n info=\"Set to True to allow loading pickle files from untrusted sources. \"\n \"Only enable this if you trust the source of the data.\",\n advanced=True,\n value=True,\n ),\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n ]\n\n @staticmethod\n def resolve_path(path: str) -> str:\n \"\"\"Resolve the path relative to the Langflow root.\n\n Args:\n path: The path to resolve\n Returns:\n str: The resolved path as a string\n \"\"\"\n return str(Path(path).resolve())\n\n def get_persist_directory(self) -> Path:\n \"\"\"Returns the resolved persist directory path or the current directory if not set.\"\"\"\n if self.persist_directory:\n return Path(self.resolve_path(self.persist_directory))\n return Path()\n\n @check_cached_vector_store\n def build_vector_store(self) -> FAISS:\n \"\"\"Builds the FAISS object.\"\"\"\n path = self.get_persist_directory()\n path.mkdir(parents=True, exist_ok=True)\n\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n faiss = FAISS.from_documents(documents=documents, embedding=self.embedding)\n faiss.save_local(str(path), self.index_name)\n return faiss\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search for documents in the FAISS vector store.\"\"\"\n path = self.get_persist_directory()\n index_path = path / f\"{self.index_name}.faiss\"\n\n if not index_path.exists():\n vector_store = self.build_vector_store()\n else:\n vector_store = FAISS.load_local(\n folder_path=str(path),\n embeddings=self.embedding,\n index_name=self.index_name,\n allow_dangerous_deserialization=self.allow_dangerous_deserialization,\n )\n\n if not vector_store:\n msg = \"Failed to load the FAISS index.\"\n raise ValueError(msg)\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n docs = vector_store.similarity_search(\n query=self.search_query,\n k=self.number_of_results,\n )\n return docs_to_data(docs)\n return []\n"},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"index_name":{"_input_type":"StrInput","advanced":false,"display_name":"Index Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"index_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"langflow_index"},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"persist_directory":{"_input_type":"StrInput","advanced":false,"display_name":"Persist Directory","dynamic":false,"info":"Path to save the FAISS index. It will be relative to where Langflow is running.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"persist_directory","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false}}],["Notion",{"AddContentToPage":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Convert markdown text to Notion blocks and append them to a Notion page.","display_name":"Add Content to Page ","documentation":"https://developers.notion.com/reference/patch-block-children","edited":false,"field_order":["markdown_text","block_id","notion_secret"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"ffcd44201c09","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"bs4","version":"4.12.3"},{"name":"langchain","version":"0.3.23"},{"name":"markdown","version":"3.7"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":6},"module":"lfx.components.Notion.add_content_to_page.AddContentToPage"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","block_id":{"_input_type":"StrInput","advanced":false,"display_name":"Page/Block ID","dynamic":false,"info":"The ID of the page/block to add the content.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"block_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"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\nfrom typing import Any\n\nimport requests\nfrom bs4 import BeautifulSoup\nfrom langchain.tools import StructuredTool\nfrom markdown import markdown\nfrom pydantic import BaseModel, Field\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import MultilineInput, SecretStrInput, StrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\nMIN_ROWS_IN_TABLE = 3\n\n\nclass AddContentToPage(LCToolComponent):\n display_name: str = \"Add Content to Page \"\n description: str = \"Convert markdown text to Notion blocks and append them to a Notion page.\"\n documentation: str = \"https://developers.notion.com/reference/patch-block-children\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n MultilineInput(\n name=\"markdown_text\",\n display_name=\"Markdown Text\",\n info=\"The markdown text to convert to Notion blocks.\",\n ),\n StrInput(\n name=\"block_id\",\n display_name=\"Page/Block ID\",\n info=\"The ID of the page/block to add the content.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n\n class AddContentToPageSchema(BaseModel):\n markdown_text: str = Field(..., description=\"The markdown text to convert to Notion blocks.\")\n block_id: str = Field(..., description=\"The ID of the page/block to add the content.\")\n\n def run_model(self) -> Data:\n result = self._add_content_to_page(self.markdown_text, self.block_id)\n return Data(data=result, text=json.dumps(result))\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"add_content_to_notion_page\",\n description=\"Convert markdown text to Notion blocks and append them to a Notion page.\",\n func=self._add_content_to_page,\n args_schema=self.AddContentToPageSchema,\n )\n\n def _add_content_to_page(self, markdown_text: str, block_id: str) -> dict[str, Any] | str:\n try:\n html_text = markdown(markdown_text)\n soup = BeautifulSoup(html_text, \"html.parser\")\n blocks = self.process_node(soup)\n\n url = f\"https://api.notion.com/v1/blocks/{block_id}/children\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"children\": blocks,\n }\n\n response = requests.patch(url, headers=headers, json=data, timeout=10)\n response.raise_for_status()\n\n return response.json()\n except requests.exceptions.RequestException as e:\n error_message = f\"Error: Failed to add content to Notion page. {e}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error adding content to Notion page\", exc_info=True)\n return f\"Error: An unexpected error occurred while adding content to Notion page. {e}\"\n\n def process_node(self, node):\n blocks = []\n if isinstance(node, str):\n text = node.strip()\n if text:\n if text.startswith(\"#\"):\n heading_level = text.count(\"#\", 0, 6)\n heading_text = text[heading_level:].strip()\n if heading_level in range(3):\n blocks.append(self.create_block(f\"heading_{heading_level + 1}\", heading_text))\n else:\n blocks.append(self.create_block(\"paragraph\", text))\n elif node.name == \"h1\":\n blocks.append(self.create_block(\"heading_1\", node.get_text(strip=True)))\n elif node.name == \"h2\":\n blocks.append(self.create_block(\"heading_2\", node.get_text(strip=True)))\n elif node.name == \"h3\":\n blocks.append(self.create_block(\"heading_3\", node.get_text(strip=True)))\n elif node.name == \"p\":\n code_node = node.find(\"code\")\n if code_node:\n code_text = code_node.get_text()\n language, code = self.extract_language_and_code(code_text)\n blocks.append(self.create_block(\"code\", code, language=language))\n elif self.is_table(str(node)):\n blocks.extend(self.process_table(node))\n else:\n blocks.append(self.create_block(\"paragraph\", node.get_text(strip=True)))\n elif node.name == \"ul\":\n blocks.extend(self.process_list(node, \"bulleted_list_item\"))\n elif node.name == \"ol\":\n blocks.extend(self.process_list(node, \"numbered_list_item\"))\n elif node.name == \"blockquote\":\n blocks.append(self.create_block(\"quote\", node.get_text(strip=True)))\n elif node.name == \"hr\":\n blocks.append(self.create_block(\"divider\", \"\"))\n elif node.name == \"img\":\n blocks.append(self.create_block(\"image\", \"\", image_url=node.get(\"src\")))\n elif node.name == \"a\":\n blocks.append(self.create_block(\"bookmark\", node.get_text(strip=True), link_url=node.get(\"href\")))\n elif node.name == \"table\":\n blocks.extend(self.process_table(node))\n\n for child in node.children:\n if isinstance(child, str):\n continue\n blocks.extend(self.process_node(child))\n\n return blocks\n\n def extract_language_and_code(self, code_text):\n lines = code_text.split(\"\\n\")\n language = lines[0].strip()\n code = \"\\n\".join(lines[1:]).strip()\n return language, code\n\n def is_code_block(self, text):\n return text.startswith(\"```\")\n\n def extract_code_block(self, text):\n lines = text.split(\"\\n\")\n language = lines[0].strip(\"`\").strip()\n code = \"\\n\".join(lines[1:]).strip(\"`\").strip()\n return language, code\n\n def is_table(self, text):\n rows = text.split(\"\\n\")\n if len(rows) < MIN_ROWS_IN_TABLE:\n return False\n\n has_separator = False\n for i, row in enumerate(rows):\n if \"|\" in row:\n cells = [cell.strip() for cell in row.split(\"|\")]\n cells = [cell for cell in cells if cell] # Remove empty cells\n if i == 1 and all(set(cell) <= set(\"-|\") for cell in cells):\n has_separator = True\n elif not cells:\n return False\n\n return has_separator\n\n def process_list(self, node, list_type):\n blocks = []\n for item in node.find_all(\"li\"):\n item_text = item.get_text(strip=True)\n checked = item_text.startswith(\"[x]\")\n is_checklist = item_text.startswith(\"[ ]\") or checked\n\n if is_checklist:\n item_text = item_text.replace(\"[x]\", \"\").replace(\"[ ]\", \"\").strip()\n blocks.append(self.create_block(\"to_do\", item_text, checked=checked))\n else:\n blocks.append(self.create_block(list_type, item_text))\n return blocks\n\n def process_table(self, node):\n blocks = []\n header_row = node.find(\"thead\").find(\"tr\") if node.find(\"thead\") else None\n body_rows = node.find(\"tbody\").find_all(\"tr\") if node.find(\"tbody\") else []\n\n if header_row or body_rows:\n table_width = max(\n len(header_row.find_all([\"th\", \"td\"])) if header_row else 0,\n *(len(row.find_all([\"th\", \"td\"])) for row in body_rows),\n )\n\n table_block = self.create_block(\"table\", \"\", table_width=table_width, has_column_header=bool(header_row))\n blocks.append(table_block)\n\n if header_row:\n header_cells = [cell.get_text(strip=True) for cell in header_row.find_all([\"th\", \"td\"])]\n header_row_block = self.create_block(\"table_row\", header_cells)\n blocks.append(header_row_block)\n\n for row in body_rows:\n cells = [cell.get_text(strip=True) for cell in row.find_all([\"th\", \"td\"])]\n row_block = self.create_block(\"table_row\", cells)\n blocks.append(row_block)\n\n return blocks\n\n def create_block(self, block_type: str, content: str, **kwargs) -> dict[str, Any]:\n block: dict[str, Any] = {\n \"object\": \"block\",\n \"type\": block_type,\n block_type: {},\n }\n\n if block_type in {\n \"paragraph\",\n \"heading_1\",\n \"heading_2\",\n \"heading_3\",\n \"bulleted_list_item\",\n \"numbered_list_item\",\n \"quote\",\n }:\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n elif block_type == \"to_do\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"checked\"] = kwargs.get(\"checked\", False)\n elif block_type == \"code\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"language\"] = kwargs.get(\"language\", \"plain text\")\n elif block_type == \"image\":\n block[block_type] = {\"type\": \"external\", \"external\": {\"url\": kwargs.get(\"image_url\", \"\")}}\n elif block_type == \"divider\":\n pass\n elif block_type == \"bookmark\":\n block[block_type][\"url\"] = kwargs.get(\"link_url\", \"\")\n elif block_type == \"table\":\n block[block_type][\"table_width\"] = kwargs.get(\"table_width\", 0)\n block[block_type][\"has_column_header\"] = kwargs.get(\"has_column_header\", False)\n block[block_type][\"has_row_header\"] = kwargs.get(\"has_row_header\", False)\n elif block_type == \"table_row\":\n block[block_type][\"cells\"] = [[{\"type\": \"text\", \"text\": {\"content\": cell}} for cell in content]]\n\n return block\n"},"markdown_text":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Markdown Text","dynamic":false,"info":"The markdown text to convert to Notion blocks.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"markdown_text","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":""},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"NotionDatabaseProperties":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Retrieve properties of a Notion database.","display_name":"List Database Properties ","documentation":"https://docs.langflow.org/bundles-notion","edited":false,"field_order":["database_id","notion_secret"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"adce99660f9e","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.Notion.list_database_properties.NotionDatabaseProperties"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 requests\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import SecretStrInput, StrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass NotionDatabaseProperties(LCToolComponent):\n display_name: str = \"List Database Properties \"\n description: str = \"Retrieve properties of a Notion database.\"\n documentation: str = \"https://docs.langflow.org/bundles-notion\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n\n class NotionDatabasePropertiesSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database.\")\n\n def run_model(self) -> Data:\n result = self._fetch_database_properties(self.database_id)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n # Success, return the properties\n return Data(text=str(result), data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_database_properties\",\n description=\"Retrieve properties of a Notion database. Input should include the database ID.\",\n func=self._fetch_database_properties,\n args_schema=self.NotionDatabasePropertiesSchema,\n )\n\n def _fetch_database_properties(self, database_id: str) -> dict | str:\n url = f\"https://api.notion.com/v1/databases/{database_id}\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\", # Use the latest supported version\n }\n try:\n response = requests.get(url, headers=headers, timeout=10)\n response.raise_for_status()\n data = response.json()\n return data.get(\"properties\", {})\n except requests.exceptions.RequestException as e:\n return f\"Error fetching Notion database properties: {e}\"\n except ValueError as e:\n return f\"Error parsing Notion API response: {e}\"\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error fetching Notion database properties\", exc_info=True)\n return f\"An unexpected error occurred: {e}\"\n"},"database_id":{"_input_type":"StrInput","advanced":false,"display_name":"Database ID","dynamic":false,"info":"The ID of the Notion database.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"database_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"NotionListPages":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Query a Notion database with filtering and sorting. The input should be a JSON string containing the 'filter' and 'sorts' objects. Example input:\n{\"filter\": {\"property\": \"Status\", \"select\": {\"equals\": \"Done\"}}, \"sorts\": [{\"timestamp\": \"created_time\", \"direction\": \"descending\"}]}","display_name":"List Pages ","documentation":"https://docs.langflow.org/bundles-notion","edited":false,"field_order":["notion_secret","database_id","query_json"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"373f9ad32937","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.Notion.list_pages.NotionListPages"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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\nfrom typing import Any\n\nimport requests\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import MultilineInput, SecretStrInput, StrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass NotionListPages(LCToolComponent):\n display_name: str = \"List Pages \"\n description: str = (\n \"Query a Notion database with filtering and sorting. \"\n \"The input should be a JSON string containing the 'filter' and 'sorts' objects. \"\n \"Example input:\\n\"\n '{\"filter\": {\"property\": \"Status\", \"select\": {\"equals\": \"Done\"}}, '\n '\"sorts\": [{\"timestamp\": \"created_time\", \"direction\": \"descending\"}]}'\n )\n documentation: str = \"https://docs.langflow.org/bundles-notion\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database to query.\",\n ),\n MultilineInput(\n name=\"query_json\",\n display_name=\"Database query (JSON)\",\n info=\"A JSON string containing the filters and sorts that will be used for querying the database. \"\n \"Leave empty for no filters or sorts.\",\n ),\n ]\n\n class NotionListPagesSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database to query.\")\n query_json: str | None = Field(\n default=\"\",\n description=\"A JSON string containing the filters and sorts for querying the database. \"\n \"Leave empty for no filters or sorts.\",\n )\n\n def run_model(self) -> list[Data]:\n result = self._query_notion_database(self.database_id, self.query_json)\n\n if isinstance(result, str):\n # An error occurred, return it as a single record\n return [Data(text=result)]\n\n records = []\n combined_text = f\"Pages found: {len(result)}\\n\\n\"\n\n for page in result:\n page_data = {\n \"id\": page[\"id\"],\n \"url\": page[\"url\"],\n \"created_time\": page[\"created_time\"],\n \"last_edited_time\": page[\"last_edited_time\"],\n \"properties\": page[\"properties\"],\n }\n\n text = (\n f\"id: {page['id']}\\n\"\n f\"url: {page['url']}\\n\"\n f\"created_time: {page['created_time']}\\n\"\n f\"last_edited_time: {page['last_edited_time']}\\n\"\n f\"properties: {json.dumps(page['properties'], indent=2)}\\n\\n\"\n )\n\n combined_text += text\n records.append(Data(text=text, **page_data))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_list_pages\",\n description=self.description,\n func=self._query_notion_database,\n args_schema=self.NotionListPagesSchema,\n )\n\n def _query_notion_database(self, database_id: str, query_json: str | None = None) -> list[dict[str, Any]] | str:\n url = f\"https://api.notion.com/v1/databases/{database_id}/query\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n query_payload = {}\n if query_json and query_json.strip():\n try:\n query_payload = json.loads(query_json)\n except json.JSONDecodeError as e:\n return f\"Invalid JSON format for query: {e}\"\n\n try:\n response = requests.post(url, headers=headers, json=query_payload, timeout=10)\n response.raise_for_status()\n results = response.json()\n return results[\"results\"]\n except requests.exceptions.RequestException as e:\n return f\"Error querying Notion database: {e}\"\n except KeyError:\n return \"Unexpected response format from Notion API\"\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error querying Notion database\", exc_info=True)\n return f\"An unexpected error occurred: {e}\"\n"},"database_id":{"_input_type":"StrInput","advanced":false,"display_name":"Database ID","dynamic":false,"info":"The ID of the Notion database to query.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"database_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"query_json":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Database query (JSON)","dynamic":false,"info":"A JSON string containing the filters and sorts that will be used for querying the database. Leave empty for no filters or sorts.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"query_json","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":""}},"tool_mode":false},"NotionPageContent":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Retrieve the content of a Notion page as plain text.","display_name":"Page Content Viewer ","documentation":"https://docs.langflow.org/bundles-notion","edited":false,"field_order":["page_id","notion_secret"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"ba15d6a01d04","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.Notion.page_content_viewer.NotionPageContent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 requests\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import SecretStrInput, StrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass NotionPageContent(LCToolComponent):\n display_name = \"Page Content Viewer \"\n description = \"Retrieve the content of a Notion page as plain text.\"\n documentation = \"https://docs.langflow.org/bundles-notion\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"page_id\",\n display_name=\"Page ID\",\n info=\"The ID of the Notion page to retrieve.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n\n class NotionPageContentSchema(BaseModel):\n page_id: str = Field(..., description=\"The ID of the Notion page to retrieve.\")\n\n def run_model(self) -> Data:\n result = self._retrieve_page_content(self.page_id)\n if isinstance(result, str) and result.startswith(\"Error:\"):\n # An error occurred, return it as text\n return Data(text=result)\n # Success, return the content\n return Data(text=result, data={\"content\": result})\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_page_content\",\n description=\"Retrieve the content of a Notion page as plain text.\",\n func=self._retrieve_page_content,\n args_schema=self.NotionPageContentSchema,\n )\n\n def _retrieve_page_content(self, page_id: str) -> str:\n blocks_url = f\"https://api.notion.com/v1/blocks/{page_id}/children?page_size=100\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\",\n }\n try:\n blocks_response = requests.get(blocks_url, headers=headers, timeout=10)\n blocks_response.raise_for_status()\n blocks_data = blocks_response.json()\n return self.parse_blocks(blocks_data.get(\"results\", []))\n except requests.exceptions.RequestException as e:\n error_message = f\"Error: Failed to retrieve Notion page content. {e}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error retrieving Notion page content\", exc_info=True)\n return f\"Error: An unexpected error occurred while retrieving Notion page content. {e}\"\n\n def parse_blocks(self, blocks: list) -> str:\n content = \"\"\n for block in blocks:\n block_type = block.get(\"type\")\n if block_type in {\"paragraph\", \"heading_1\", \"heading_2\", \"heading_3\", \"quote\"}:\n content += self.parse_rich_text(block[block_type].get(\"rich_text\", [])) + \"\\n\\n\"\n elif block_type in {\"bulleted_list_item\", \"numbered_list_item\"}:\n content += self.parse_rich_text(block[block_type].get(\"rich_text\", [])) + \"\\n\"\n elif block_type == \"to_do\":\n content += self.parse_rich_text(block[\"to_do\"].get(\"rich_text\", [])) + \"\\n\"\n elif block_type == \"code\":\n content += self.parse_rich_text(block[\"code\"].get(\"rich_text\", [])) + \"\\n\\n\"\n elif block_type == \"image\":\n content += f\"[Image: {block['image'].get('external', {}).get('url', 'No URL')}]\\n\\n\"\n elif block_type == \"divider\":\n content += \"---\\n\\n\"\n return content.strip()\n\n def parse_rich_text(self, rich_text: list) -> str:\n return \"\".join(segment.get(\"plain_text\", \"\") for segment in rich_text)\n\n def __call__(self, *args, **kwargs):\n return self._retrieve_page_content(*args, **kwargs)\n"},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"page_id":{"_input_type":"StrInput","advanced":false,"display_name":"Page ID","dynamic":false,"info":"The ID of the Notion page to retrieve.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"page_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"NotionPageCreator":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"A component for creating Notion pages.","display_name":"Create Page ","documentation":"https://docs.langflow.org/bundles-notion","edited":false,"field_order":["database_id","notion_secret","properties_json"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"640438ed3d7b","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.Notion.create_page.NotionPageCreator"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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\nfrom typing import Any\n\nimport requests\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import MultilineInput, SecretStrInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass NotionPageCreator(LCToolComponent):\n display_name: str = \"Create Page \"\n description: str = \"A component for creating Notion pages.\"\n documentation: str = \"https://docs.langflow.org/bundles-notion\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n MultilineInput(\n name=\"properties_json\",\n display_name=\"Properties (JSON)\",\n info=\"The properties of the new page as a JSON string.\",\n ),\n ]\n\n class NotionPageCreatorSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database.\")\n properties_json: str = Field(..., description=\"The properties of the new page as a JSON string.\")\n\n def run_model(self) -> Data:\n result = self._create_notion_page(self.database_id, self.properties_json)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n # Success, return the created page data\n output = \"Created page properties:\\n\"\n for prop_name, prop_value in result.get(\"properties\", {}).items():\n output += f\"{prop_name}: {prop_value}\\n\"\n return Data(text=output, data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"create_notion_page\",\n description=\"Create a new page in a Notion database. \"\n \"IMPORTANT: Use the tool to check the Database properties for more details before using this tool.\",\n func=self._create_notion_page,\n args_schema=self.NotionPageCreatorSchema,\n )\n\n def _create_notion_page(self, database_id: str, properties_json: str) -> dict[str, Any] | str:\n if not database_id or not properties_json:\n return \"Invalid input. Please provide 'database_id' and 'properties_json'.\"\n\n try:\n properties = json.loads(properties_json)\n except json.JSONDecodeError as e:\n return f\"Invalid properties format. Please provide a valid JSON string. Error: {e}\"\n\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"parent\": {\"database_id\": database_id},\n \"properties\": properties,\n }\n\n try:\n response = requests.post(\"https://api.notion.com/v1/pages\", headers=headers, json=data, timeout=10)\n response.raise_for_status()\n return response.json()\n except requests.exceptions.RequestException as e:\n error_message = f\"Failed to create Notion page. Error: {e}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n\n def __call__(self, *args, **kwargs):\n return self._create_notion_page(*args, **kwargs)\n"},"database_id":{"_input_type":"StrInput","advanced":false,"display_name":"Database ID","dynamic":false,"info":"The ID of the Notion database.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"database_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"properties_json":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Properties (JSON)","dynamic":false,"info":"The properties of the new page as a JSON string.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"properties_json","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":""}},"tool_mode":false},"NotionPageUpdate":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Update the properties of a Notion page.","display_name":"Update Page Property ","documentation":"https://docs.langflow.org/bundles-notion","edited":false,"field_order":["page_id","properties","notion_secret"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"32ccdf34df73","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.Notion.update_page_property.NotionPageUpdate"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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\nfrom typing import Any\n\nimport requests\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import MultilineInput, SecretStrInput, StrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass NotionPageUpdate(LCToolComponent):\n display_name: str = \"Update Page Property \"\n description: str = \"Update the properties of a Notion page.\"\n documentation: str = \"https://docs.langflow.org/bundles-notion\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"page_id\",\n display_name=\"Page ID\",\n info=\"The ID of the Notion page to update.\",\n ),\n MultilineInput(\n name=\"properties\",\n display_name=\"Properties\",\n info=\"The properties to update on the page (as a JSON string or a dictionary).\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n\n class NotionPageUpdateSchema(BaseModel):\n page_id: str = Field(..., description=\"The ID of the Notion page to update.\")\n properties: str | dict[str, Any] = Field(\n ..., description=\"The properties to update on the page (as a JSON string or a dictionary).\"\n )\n\n def run_model(self) -> Data:\n result = self._update_notion_page(self.page_id, self.properties)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n # Success, return the updated page data\n output = \"Updated page properties:\\n\"\n for prop_name, prop_value in result.get(\"properties\", {}).items():\n output += f\"{prop_name}: {prop_value}\\n\"\n return Data(text=output, data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"update_notion_page\",\n description=\"Update the properties of a Notion page. \"\n \"IMPORTANT: Use the tool to check the Database properties for more details before using this tool.\",\n func=self._update_notion_page,\n args_schema=self.NotionPageUpdateSchema,\n )\n\n def _update_notion_page(self, page_id: str, properties: str | dict[str, Any]) -> dict[str, Any] | str:\n url = f\"https://api.notion.com/v1/pages/{page_id}\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\", # Use the latest supported version\n }\n\n # Parse properties if it's a string\n if isinstance(properties, str):\n try:\n parsed_properties = json.loads(properties)\n except json.JSONDecodeError as e:\n error_message = f\"Invalid JSON format for properties: {e}\"\n logger.exception(error_message)\n return error_message\n\n else:\n parsed_properties = properties\n\n data = {\"properties\": parsed_properties}\n\n try:\n logger.info(f\"Sending request to Notion API: URL: {url}, Data: {json.dumps(data)}\")\n response = requests.patch(url, headers=headers, json=data, timeout=10)\n response.raise_for_status()\n updated_page = response.json()\n\n logger.info(f\"Successfully updated Notion page. Response: {json.dumps(updated_page)}\")\n except requests.exceptions.HTTPError as e:\n error_message = f\"HTTP Error occurred: {e}\"\n if e.response is not None:\n error_message += f\"\\nStatus code: {e.response.status_code}\"\n error_message += f\"\\nResponse body: {e.response.text}\"\n logger.exception(error_message)\n return error_message\n except requests.exceptions.RequestException as e:\n error_message = f\"An error occurred while making the request: {e}\"\n logger.exception(error_message)\n return error_message\n except Exception as e: # noqa: BLE001\n error_message = f\"An unexpected error occurred: {e}\"\n logger.exception(error_message)\n return error_message\n\n return updated_page\n\n def __call__(self, *args, **kwargs):\n return self._update_notion_page(*args, **kwargs)\n"},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"page_id":{"_input_type":"StrInput","advanced":false,"display_name":"Page ID","dynamic":false,"info":"The ID of the Notion page to update.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"page_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"properties":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Properties","dynamic":false,"info":"The properties to update on the page (as a JSON string or a dictionary).","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"properties","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":""}},"tool_mode":false},"NotionSearch":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Searches all pages and databases that have been shared with an integration.","display_name":"Search ","documentation":"https://docs.langflow.org/bundles-notion","edited":false,"field_order":["notion_secret","query","filter_value","sort_direction"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"793b8818a3b4","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.Notion.search.NotionSearch"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from typing import Any\n\nimport requests\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import DropdownInput, SecretStrInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass NotionSearch(LCToolComponent):\n display_name: str = \"Search \"\n description: str = \"Searches all pages and databases that have been shared with an integration.\"\n documentation: str = \"https://docs.langflow.org/bundles-notion\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n StrInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The text that the API compares page and database titles against.\",\n ),\n DropdownInput(\n name=\"filter_value\",\n display_name=\"Filter Type\",\n info=\"Limits the results to either only pages or only databases.\",\n options=[\"page\", \"database\"],\n value=\"page\",\n ),\n DropdownInput(\n name=\"sort_direction\",\n display_name=\"Sort Direction\",\n info=\"The direction to sort the results.\",\n options=[\"ascending\", \"descending\"],\n value=\"descending\",\n ),\n ]\n\n class NotionSearchSchema(BaseModel):\n query: str = Field(..., description=\"The search query text.\")\n filter_value: str = Field(default=\"page\", description=\"Filter type: 'page' or 'database'.\")\n sort_direction: str = Field(default=\"descending\", description=\"Sort direction: 'ascending' or 'descending'.\")\n\n def run_model(self) -> list[Data]:\n results = self._search_notion(self.query, self.filter_value, self.sort_direction)\n records = []\n combined_text = f\"Results found: {len(results)}\\n\\n\"\n\n for result in results:\n result_data = {\n \"id\": result[\"id\"],\n \"type\": result[\"object\"],\n \"last_edited_time\": result[\"last_edited_time\"],\n }\n\n if result[\"object\"] == \"page\":\n result_data[\"title_or_url\"] = result[\"url\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['url']}\\n\"\n elif result[\"object\"] == \"database\":\n if \"title\" in result and isinstance(result[\"title\"], list) and len(result[\"title\"]) > 0:\n result_data[\"title_or_url\"] = result[\"title\"][0][\"plain_text\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['title'][0]['plain_text']}\\n\"\n else:\n result_data[\"title_or_url\"] = \"N/A\"\n text = f\"id: {result['id']}\\ntitle_or_url: N/A\\n\"\n\n text += f\"type: {result['object']}\\nlast_edited_time: {result['last_edited_time']}\\n\\n\"\n combined_text += text\n records.append(Data(text=text, data=result_data))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_search\",\n description=\"Search Notion pages and databases. \"\n \"Input should include the search query and optionally filter type and sort direction.\",\n func=self._search_notion,\n args_schema=self.NotionSearchSchema,\n )\n\n def _search_notion(\n self, query: str, filter_value: str = \"page\", sort_direction: str = \"descending\"\n ) -> list[dict[str, Any]]:\n url = \"https://api.notion.com/v1/search\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"query\": query,\n \"filter\": {\"value\": filter_value, \"property\": \"object\"},\n \"sort\": {\"direction\": sort_direction, \"timestamp\": \"last_edited_time\"},\n }\n\n response = requests.post(url, headers=headers, json=data, timeout=10)\n response.raise_for_status()\n\n results = response.json()\n return results[\"results\"]\n"},"filter_value":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Filter Type","dynamic":false,"external_options":{},"info":"Limits the results to either only pages or only databases.","name":"filter_value","options":["page","database"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"page"},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"query":{"_input_type":"StrInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"The text that the API compares page and database titles against.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"query","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"sort_direction":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Sort Direction","dynamic":false,"external_options":{},"info":"The direction to sort the results.","name":"sort_direction","options":["ascending","descending"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"descending"}},"tool_mode":false},"NotionUserList":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Retrieve users from Notion.","display_name":"List Users ","documentation":"https://docs.langflow.org/bundles-notion","edited":false,"field_order":["notion_secret"],"frozen":false,"icon":"NotionDirectoryLoader","legacy":false,"metadata":{"code_hash":"8966397da1d5","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.Notion.list_users.NotionUserList"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 requests\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import SecretStrInput\nfrom lfx.schema.data import Data\n\n\nclass NotionUserList(LCToolComponent):\n display_name = \"List Users \"\n description = \"Retrieve users from Notion.\"\n documentation = \"https://docs.langflow.org/bundles-notion\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n\n class NotionUserListSchema(BaseModel):\n pass\n\n def run_model(self) -> list[Data]:\n users = self._list_users()\n records = []\n combined_text = \"\"\n\n for user in users:\n output = \"User:\\n\"\n for key, value in user.items():\n output += f\"{key.replace('_', ' ').title()}: {value}\\n\"\n output += \"________________________\\n\"\n\n combined_text += output\n records.append(Data(text=output, data=user))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_list_users\",\n description=\"Retrieve users from Notion.\",\n func=self._list_users,\n args_schema=self.NotionUserListSchema,\n )\n\n def _list_users(self) -> list[dict]:\n url = \"https://api.notion.com/v1/users\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n response = requests.get(url, headers=headers, timeout=10)\n response.raise_for_status()\n\n data = response.json()\n results = data[\"results\"]\n\n users = []\n for user in results:\n user_data = {\n \"id\": user[\"id\"],\n \"type\": user[\"type\"],\n \"name\": user.get(\"name\", \"\"),\n \"avatar_url\": user.get(\"avatar_url\", \"\"),\n }\n users.append(user_data)\n\n return users\n"},"notion_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Notion Secret","dynamic":false,"info":"The Notion integration token.","input_types":[],"load_from_db":true,"name":"notion_secret","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false}}],["agentql",{"AgentQL":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Extracts structured data from a web page using an AgentQL query or a Natural Language description.","display_name":"Extract Web Data","documentation":"https://docs.agentql.com/rest-api/api-reference","edited":false,"field_order":["api_key","url","query","prompt","is_stealth_mode_enabled","timeout","mode","wait_for","is_scroll_to_bottom_enabled","is_screenshot_enabled"],"frozen":false,"icon":"AgentQL","legacy":false,"metadata":{"code_hash":"37de3210aed9","dependencies":{"dependencies":[{"name":"httpx","version":"0.28.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.agentql.agentql_api.AgentQL"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"build_output","name":"data","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AgentQL API Key","dynamic":false,"info":"Your AgentQL API key from dev.agentql.com","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 httpx\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import BoolInput, DropdownInput, IntInput, MessageTextInput, MultilineInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass AgentQL(Component):\n display_name = \"Extract Web Data\"\n description = \"Extracts structured data from a web page using an AgentQL query or a Natural Language description.\"\n documentation: str = \"https://docs.agentql.com/rest-api/api-reference\"\n icon = \"AgentQL\"\n name = \"AgentQL\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"AgentQL API Key\",\n required=True,\n password=True,\n info=\"Your AgentQL API key from dev.agentql.com\",\n ),\n MessageTextInput(\n name=\"url\",\n display_name=\"URL\",\n required=True,\n info=\"The URL of the public web page you want to extract data from.\",\n tool_mode=True,\n ),\n MultilineInput(\n name=\"query\",\n display_name=\"AgentQL Query\",\n required=False,\n info=\"The AgentQL query to execute. Learn more at https://docs.agentql.com/agentql-query or use a prompt.\",\n tool_mode=True,\n ),\n MultilineInput(\n name=\"prompt\",\n display_name=\"Prompt\",\n required=False,\n info=\"A Natural Language description of the data to extract from the page. Alternative to AgentQL query.\",\n tool_mode=True,\n ),\n BoolInput(\n name=\"is_stealth_mode_enabled\",\n display_name=\"Enable Stealth Mode (Beta)\",\n info=\"Enable experimental anti-bot evasion strategies. May not work for all websites at all times.\",\n value=False,\n advanced=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"Seconds to wait for a request.\",\n value=900,\n advanced=True,\n ),\n DropdownInput(\n name=\"mode\",\n display_name=\"Request Mode\",\n info=\"'standard' uses deep data analysis, while 'fast' trades some depth of analysis for speed.\",\n options=[\"fast\", \"standard\"],\n value=\"fast\",\n advanced=True,\n ),\n IntInput(\n name=\"wait_for\",\n display_name=\"Wait For\",\n info=\"Seconds to wait for the page to load before extracting data.\",\n value=0,\n range_spec=RangeSpec(min=0, max=10, step_type=\"int\"),\n advanced=True,\n ),\n BoolInput(\n name=\"is_scroll_to_bottom_enabled\",\n display_name=\"Enable scroll to bottom\",\n info=\"Scroll to bottom of the page before extracting data.\",\n value=False,\n advanced=True,\n ),\n BoolInput(\n name=\"is_screenshot_enabled\",\n display_name=\"Enable screenshot\",\n info=\"Take a screenshot before extracting data. Returned in 'metadata' as a Base64 string.\",\n value=False,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"build_output\"),\n ]\n\n def build_output(self) -> Data:\n endpoint = \"https://api.agentql.com/v1/query-data\"\n headers = {\n \"X-API-Key\": self.api_key,\n \"Content-Type\": \"application/json\",\n \"X-TF-Request-Origin\": \"langflow\",\n }\n\n payload = {\n \"url\": self.url,\n \"query\": self.query,\n \"prompt\": self.prompt,\n \"params\": {\n \"mode\": self.mode,\n \"wait_for\": self.wait_for,\n \"is_scroll_to_bottom_enabled\": self.is_scroll_to_bottom_enabled,\n \"is_screenshot_enabled\": self.is_screenshot_enabled,\n },\n \"metadata\": {\n \"experimental_stealth_mode_enabled\": self.is_stealth_mode_enabled,\n },\n }\n\n if not self.prompt and not self.query:\n self.status = \"Either Query or Prompt must be provided.\"\n raise ValueError(self.status)\n if self.prompt and self.query:\n self.status = \"Both Query and Prompt can't be provided at the same time.\"\n raise ValueError(self.status)\n\n try:\n response = httpx.post(endpoint, headers=headers, json=payload, timeout=self.timeout)\n response.raise_for_status()\n\n json = response.json()\n data = Data(result=json[\"data\"], metadata=json[\"metadata\"])\n\n except httpx.HTTPStatusError as e:\n response = e.response\n if response.status_code == httpx.codes.UNAUTHORIZED:\n self.status = \"Please, provide a valid API Key. You can create one at https://dev.agentql.com.\"\n else:\n try:\n error_json = response.json()\n logger.error(\n f\"Failure response: '{response.status_code} {response.reason_phrase}' with body: {error_json}\"\n )\n msg = error_json[\"error_info\"] if \"error_info\" in error_json else error_json[\"detail\"]\n except (ValueError, TypeError):\n msg = f\"HTTP {e}.\"\n self.status = msg\n raise ValueError(self.status) from e\n\n else:\n self.status = data\n return data\n"},"is_screenshot_enabled":{"_input_type":"BoolInput","advanced":true,"display_name":"Enable screenshot","dynamic":false,"info":"Take a screenshot before extracting data. Returned in 'metadata' as a Base64 string.","list":false,"list_add_label":"Add More","name":"is_screenshot_enabled","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"is_scroll_to_bottom_enabled":{"_input_type":"BoolInput","advanced":true,"display_name":"Enable scroll to bottom","dynamic":false,"info":"Scroll to bottom of the page before extracting data.","list":false,"list_add_label":"Add More","name":"is_scroll_to_bottom_enabled","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"is_stealth_mode_enabled":{"_input_type":"BoolInput","advanced":true,"display_name":"Enable Stealth Mode (Beta)","dynamic":false,"info":"Enable experimental anti-bot evasion strategies. May not work for all websites at all times.","list":false,"list_add_label":"Add More","name":"is_stealth_mode_enabled","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"mode":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Request Mode","dynamic":false,"external_options":{},"info":"'standard' uses deep data analysis, while 'fast' trades some depth of analysis for speed.","name":"mode","options":["fast","standard"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"fast"},"prompt":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Prompt","dynamic":false,"info":"A Natural Language description of the data to extract from the page. Alternative to AgentQL query.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"prompt","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"query":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"AgentQL Query","dynamic":false,"info":"The AgentQL query to execute. Learn more at https://docs.agentql.com/agentql-query or use a prompt.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"query","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"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":"Seconds to wait for a request.","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":900},"url":{"_input_type":"MessageTextInput","advanced":false,"display_name":"URL","dynamic":false,"info":"The URL of the public web page you want to extract data from.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"url","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"wait_for":{"_input_type":"IntInput","advanced":true,"display_name":"Wait For","dynamic":false,"info":"Seconds to wait for the page to load before extracting data.","list":false,"list_add_label":"Add More","name":"wait_for","override_skip":false,"placeholder":"","range_spec":{"max":10.0,"min":0.0,"step":0.1,"step_type":"int"},"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int","value":0}},"tool_mode":false}}],["aiml",{"AIMLEmbeddings":{"base_classes":["Embeddings"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate embeddings using the AI/ML API.","display_name":"AI/ML API Embeddings","documentation":"","edited":false,"field_order":["model_name","aiml_api_key"],"frozen":false,"icon":"AIML","legacy":false,"metadata":{"code_hash":"dae370391ba3","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.aiml.aiml_embeddings.AIMLEmbeddingsComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Embedding Model","group_outputs":false,"method":"build_embeddings","name":"embeddings","selected":"Embeddings","tool_mode":true,"types":["Embeddings"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","aiml_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AI/ML API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"aiml_api_key","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AIML_API_KEY"},"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":"from lfx.base.embeddings.aiml_embeddings import AIMLEmbeddingsImpl\nfrom lfx.base.embeddings.model import LCEmbeddingsModel\nfrom lfx.field_typing import Embeddings\nfrom lfx.inputs.inputs import DropdownInput\nfrom lfx.io import SecretStrInput\n\n\nclass AIMLEmbeddingsComponent(LCEmbeddingsModel):\n display_name = \"AI/ML API Embeddings\"\n description = \"Generate embeddings using the AI/ML API.\"\n icon = \"AIML\"\n name = \"AIMLEmbeddings\"\n\n inputs = [\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=[\n \"text-embedding-3-small\",\n \"text-embedding-3-large\",\n \"text-embedding-ada-002\",\n ],\n required=True,\n ),\n SecretStrInput(\n name=\"aiml_api_key\",\n display_name=\"AI/ML API Key\",\n value=\"AIML_API_KEY\",\n required=True,\n ),\n ]\n\n def build_embeddings(self) -> Embeddings:\n return AIMLEmbeddingsImpl(\n api_key=self.aiml_api_key,\n model=self.model_name,\n )\n"},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model Name","dynamic":false,"external_options":{},"info":"","name":"model_name","options":["text-embedding-3-small","text-embedding-3-large","text-embedding-ada-002"],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""}},"tool_mode":false},"AIMLModel":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generates text using AI/ML API LLMs.","display_name":"AI/ML API","documentation":"https://docs.aimlapi.com/api-reference","edited":false,"field_order":["input_value","system_message","stream","max_tokens","model_kwargs","model_name","aiml_api_base","api_key","temperature"],"frozen":false,"icon":"AIML","legacy":false,"metadata":{"code_hash":"db72277a0d5a","dependencies":{"dependencies":[{"name":"langchain_openai","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"typing_extensions","version":"4.15.0"},{"name":"lfx","version":null},{"name":"openai","version":"1.82.1"}],"total_dependencies":5},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.aiml.aiml.AIMLModelComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","aiml_api_base":{"_input_type":"StrInput","advanced":true,"display_name":"AI/ML API Base","dynamic":false,"info":"The base URL of the API. Defaults to https://api.aimlapi.com . You can change this to use other APIs like JinaChat, LocalAI and Prem.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"aiml_api_base","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AI/ML API Key","dynamic":false,"info":"The AI/ML API Key to use for the OpenAI model.","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":"AIML_API_KEY"},"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":"from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\nfrom typing_extensions import override\n\nfrom lfx.base.models.aiml_constants import AimlModels\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import (\n DictInput,\n DropdownInput,\n IntInput,\n SecretStrInput,\n SliderInput,\n StrInput,\n)\n\n\nclass AIMLModelComponent(LCModelComponent):\n display_name = \"AI/ML API\"\n description = \"Generates text using AI/ML API LLMs.\"\n icon = \"AIML\"\n name = \"AIMLModel\"\n documentation = \"https://docs.aimlapi.com/api-reference\"\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=[],\n refresh_button=True,\n ),\n StrInput(\n name=\"aiml_api_base\",\n display_name=\"AI/ML API Base\",\n advanced=True,\n info=\"The base URL of the API. Defaults to https://api.aimlapi.com . \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"AI/ML API Key\",\n info=\"The AI/ML API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"AIML_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=2, step=0.01),\n ),\n ]\n\n @override\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n if field_name in {\"api_key\", \"aiml_api_base\", \"model_name\"}:\n aiml = AimlModels()\n aiml.get_aiml_models()\n build_config[\"model_name\"][\"options\"] = aiml.chat_models\n return build_config\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n aiml_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n aiml_api_base = self.aiml_api_base or \"https://api.aimlapi.com/v2\"\n\n openai_api_key = aiml_api_key.get_secret_value() if isinstance(aiml_api_key, SecretStr) else aiml_api_key\n\n # TODO: Once OpenAI fixes their o1 models, this part will need to be removed\n # to work correctly with o1 temperature settings.\n if \"o1\" in model_name:\n temperature = 1\n\n return ChatOpenAI(\n model=model_name,\n temperature=temperature,\n api_key=openai_api_key,\n base_url=aiml_api_base,\n max_tokens=max_tokens or None,\n **model_kwargs,\n )\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai.error import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.json_body.get(\"error\", {}).get(\"message\", \"\")\n if message:\n return message\n return None\n"},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Max Tokens","dynamic":false,"info":"The maximum number of tokens to generate. Set to 0 for unlimited tokens.","list":false,"list_add_label":"Add More","name":"max_tokens","override_skip":false,"placeholder":"","range_spec":{"max":128000.0,"min":0.0,"step":0.1,"step_type":"float"},"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int","value":""},"model_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Model Kwargs","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"model_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model Name","dynamic":false,"external_options":{},"info":"","name":"model_name","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","refresh_button":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"SliderInput","advanced":false,"display_name":"Temperature","dynamic":false,"info":"","max_label":"","max_label_icon":"","min_label":"","min_label_icon":"","name":"temperature","override_skip":false,"placeholder":"","range_spec":{"max":2.0,"min":0.0,"step":0.01,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":0.1}},"tool_mode":false}}],["altk",{"ALTK Agent":{"base_classes":["Message"],"beta":true,"conditional_paths":[],"custom_fields":{},"description":"Advanced agent with both pre-tool validation and post-tool processing capabilities.","display_name":"ALTK Agent","documentation":"https://docs.langflow.org/bundles-altk","edited":false,"field_order":["agent_llm","model","api_key","system_prompt","context_id","n_messages","format_instructions","output_schema","tools","input_value","handle_parsing_errors","verbose","max_iterations","agent_description","add_current_date_tool","enable_tool_validation","enable_post_tool_reflection","response_processing_size_threshold"],"frozen":false,"icon":"zap","legacy":false,"metadata":{"code_hash":"d1caf0d1db88","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_core","version":"0.3.80"}],"total_dependencies":2},"module":"lfx.components.altk.altk_agent.ALTKAgentComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Response","group_outputs":false,"method":"message_response","name":"response","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","add_current_date_tool":{"_input_type":"BoolInput","advanced":true,"display_name":"Current Date","dynamic":false,"info":"If true, will add a tool to the agent that returns the current date.","list":false,"list_add_label":"Add More","name":"add_current_date_tool","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"agent_description":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Agent Description [Deprecated]","dynamic":false,"info":"The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"agent_description","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":"A helpful assistant with access to the following tools:"},"agent_llm":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model Provider","dynamic":false,"external_options":{},"info":"The provider of the language model that the agent will use to generate responses.","input_types":[],"name":"agent_llm","options":["Anthropic","OpenAI"],"options_metadata":[{"icon":"Anthropic"},{"icon":"OpenAI"}],"override_skip":false,"placeholder":"","real_time_refresh":true,"refresh_button":false,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"OpenAI"},"api_key":{"_input_type":"SecretStrInput","advanced":true,"display_name":"API Key","dynamic":false,"info":"Model Provider API key","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"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":"\"\"\"ALTK Agent Component that combines pre-tool validation and post-tool processing capabilities.\"\"\"\n\nfrom lfx.base.agents.altk_base_agent import ALTKBaseAgentComponent\nfrom lfx.base.agents.altk_tool_wrappers import (\n PostToolProcessingWrapper,\n PreToolValidationWrapper,\n)\nfrom lfx.base.models.model_input_constants import MODEL_PROVIDERS_DICT, MODELS_METADATA\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.inputs.inputs import BoolInput\nfrom lfx.io import DropdownInput, IntInput, Output\nfrom lfx.log.logger import logger\n\n\ndef set_advanced_true(component_input):\n \"\"\"Set the advanced flag to True for a component input.\"\"\"\n component_input.advanced = True\n return component_input\n\n\nMODEL_PROVIDERS_LIST = [\"Anthropic\", \"OpenAI\"]\nINPUT_NAMES_TO_BE_OVERRIDDEN = [\"agent_llm\"]\n\n\ndef get_parent_agent_inputs():\n return [\n input_field\n for input_field in ALTKBaseAgentComponent.inputs\n if input_field.name not in INPUT_NAMES_TO_BE_OVERRIDDEN\n ]\n\n\n# === Combined ALTK Agent Component ===\n\n\nclass ALTKAgentComponent(ALTKBaseAgentComponent):\n \"\"\"ALTK Agent with both pre-tool validation and post-tool processing capabilities.\n\n This agent combines the functionality of both ALTKAgent and AgentReflection components,\n implementing a modular pipeline for tool processing that can be extended with\n additional capabilities in the future.\n \"\"\"\n\n display_name: str = \"ALTK Agent\"\n description: str = \"Advanced agent with both pre-tool validation and post-tool processing capabilities.\"\n documentation: str = \"https://docs.langflow.org/bundles-altk\"\n icon = \"zap\"\n beta = True\n name = \"ALTK Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n # Filter out json_mode from OpenAI inputs since we handle structured output differently\n if \"OpenAI\" in MODEL_PROVIDERS_DICT:\n openai_inputs_filtered = [\n input_field\n for input_field in MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"]\n if not (hasattr(input_field, \"name\") and input_field.name == \"json_mode\")\n ]\n else:\n openai_inputs_filtered = []\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST],\n value=\"OpenAI\",\n real_time_refresh=True,\n refresh_button=False,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST if key in MODELS_METADATA],\n ),\n *get_parent_agent_inputs(),\n BoolInput(\n name=\"enable_tool_validation\",\n display_name=\"Tool Validation\",\n info=\"Validates tool calls using SPARC before execution.\",\n value=True,\n ),\n BoolInput(\n name=\"enable_post_tool_reflection\",\n display_name=\"Post Tool JSON Processing\",\n info=\"Processes tool output through JSON analysis.\",\n value=True,\n ),\n IntInput(\n name=\"response_processing_size_threshold\",\n display_name=\"Response Processing Size Threshold\",\n value=100,\n info=\"Tool output is post-processed only if response exceeds this character threshold.\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n def configure_tool_pipeline(self) -> None:\n \"\"\"Configure the tool pipeline with wrappers based on enabled features.\"\"\"\n wrappers = []\n\n # Add post-tool processing first (innermost wrapper)\n if self.enable_post_tool_reflection:\n logger.info(\"Enabling Post-Tool Processing Wrapper!\")\n post_processor = PostToolProcessingWrapper(\n response_processing_size_threshold=self.response_processing_size_threshold\n )\n wrappers.append(post_processor)\n\n # Add pre-tool validation last (outermost wrapper)\n if self.enable_tool_validation:\n logger.info(\"Enabling Pre-Tool Validation Wrapper!\")\n pre_validator = PreToolValidationWrapper()\n wrappers.append(pre_validator)\n\n self.pipeline_manager.configure_wrappers(wrappers)\n\n def update_runnable_instance(self, agent, runnable, tools):\n \"\"\"Override to add tool specs update for validation wrappers.\"\"\"\n # Get context info (copied from parent)\n user_query = self.get_user_query()\n conversation_context = self.build_conversation_context()\n\n # Initialize pipeline (this ensures configure_tool_pipeline is called)\n self._initialize_tool_pipeline()\n\n # Update tool specs for validation wrappers BEFORE processing\n for wrapper in self.pipeline_manager.wrappers:\n if isinstance(wrapper, PreToolValidationWrapper) and tools:\n wrapper.tool_specs = wrapper.convert_langchain_tools_to_sparc_tool_specs_format(tools)\n\n # Process tools with updated specs\n processed_tools = self.pipeline_manager.process_tools(\n list(tools or []),\n agent=agent,\n user_query=user_query,\n conversation_context=conversation_context,\n )\n\n runnable.tools = processed_tools\n return runnable\n\n def __init__(self, **kwargs):\n \"\"\"Initialize ALTK agent with input normalization for Data.to_lc_message() inconsistencies.\"\"\"\n super().__init__(**kwargs)\n\n # If input_value uses Data.to_lc_message(), wrap it to provide consistent content\n if hasattr(self.input_value, \"to_lc_message\") and callable(self.input_value.to_lc_message):\n self.input_value = self._create_normalized_input_proxy(self.input_value)\n\n def _create_normalized_input_proxy(self, original_input):\n \"\"\"Create a proxy that normalizes to_lc_message() content format.\"\"\"\n\n class NormalizedInputProxy:\n def __init__(self, original):\n self._original = original\n\n def __getattr__(self, name):\n if name == \"to_lc_message\":\n return self._normalized_to_lc_message\n return getattr(self._original, name)\n\n def _normalized_to_lc_message(self):\n \"\"\"Return a message with normalized string content.\"\"\"\n original_msg = self._original.to_lc_message()\n\n # If content is in list format, normalize it to string\n if hasattr(original_msg, \"content\") and isinstance(original_msg.content, list):\n from langchain_core.messages import AIMessage, HumanMessage\n\n from lfx.base.agents.altk_base_agent import (\n normalize_message_content,\n )\n\n normalized_content = normalize_message_content(original_msg)\n\n # Create new message with string content\n if isinstance(original_msg, HumanMessage):\n return HumanMessage(content=normalized_content)\n return AIMessage(content=normalized_content)\n\n # Return original if already string format\n return original_msg\n\n def __str__(self):\n return str(self._original)\n\n def __repr__(self):\n return f\"NormalizedInputProxy({self._original!r})\"\n\n return NormalizedInputProxy(original_input)\n"},"context_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Context ID","dynamic":false,"info":"The context ID of the chat. Adds an extra layer to the local memory.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"context_id","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":""},"enable_post_tool_reflection":{"_input_type":"BoolInput","advanced":false,"display_name":"Post Tool JSON Processing","dynamic":false,"info":"Processes tool output through JSON analysis.","list":false,"list_add_label":"Add More","name":"enable_post_tool_reflection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"enable_tool_validation":{"_input_type":"BoolInput","advanced":false,"display_name":"Tool Validation","dynamic":false,"info":"Validates tool calls using SPARC before execution.","list":false,"list_add_label":"Add More","name":"enable_tool_validation","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"format_instructions":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Output Format Instructions","dynamic":false,"info":"Generic Template for structured output formatting. Valid only with Structured response.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"format_instructions","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":"You are an AI that extracts structured JSON objects from unstructured text. Use a predefined schema with expected types (str, int, float, bool, dict). Extract ALL relevant instances that match the schema - if multiple patterns exist, capture them all. Fill missing or ambiguous values with defaults: null for missing values. Remove exact duplicates but keep variations that have different field values. Always return valid JSON in the expected format, never throw errors. If multiple objects can be extracted, return them all in the structured format."},"handle_parsing_errors":{"_input_type":"BoolInput","advanced":true,"display_name":"Handle Parse Errors","dynamic":false,"info":"Should the Agent fix errors when reading user input for better processing?","list":false,"list_add_label":"Add More","name":"handle_parsing_errors","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"The input provided by the user for the agent to process.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"max_iterations":{"_input_type":"IntInput","advanced":true,"display_name":"Max Iterations","dynamic":false,"info":"The maximum number of attempts the agent can make to complete its task before it stops.","list":false,"list_add_label":"Add More","name":"max_iterations","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":15},"model":{"_input_type":"ModelInput","advanced":false,"display_name":"Language Model","dynamic":false,"external_options":{"fields":{"data":{"node":{"display_name":"Connect other models","icon":"CornerDownLeft","name":"connect_other_models"}}}},"info":"Select your model provider","input_types":["LanguageModel"],"list":false,"list_add_label":"Add More","model_type":"language","name":"model","override_skip":false,"placeholder":"Setup Provider","real_time_refresh":true,"refresh_button":true,"required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"model","value":""},"n_messages":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Chat History Messages","dynamic":false,"info":"Number of chat history messages to retrieve.","list":false,"list_add_label":"Add More","name":"n_messages","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":100},"output_schema":{"_input_type":"TableInput","advanced":true,"display_name":"Output Schema","dynamic":false,"info":"Schema Validation: Define the structure and data types for structured output. No validation if no output schema.","is_list":true,"list_add_label":"Add More","name":"output_schema","override_skip":false,"placeholder":"","required":false,"show":true,"table_icon":"Table","table_schema":[{"default":"field","description":"Specify the name of the output field.","display_name":"Name","edit_mode":"inline","name":"name","type":"str"},{"default":"description of field","description":"Describe the purpose of the output field.","display_name":"Description","edit_mode":"popover","name":"description","type":"str"},{"default":"str","description":"Indicate the data type of the output field (e.g., str, int, float, bool, dict).","display_name":"Type","edit_mode":"inline","name":"type","options":["str","int","float","bool","dict"],"type":"str"},{"default":"False","description":"Set to True if this output field should be a list of the specified type.","display_name":"As List","edit_mode":"inline","name":"multiple","type":"boolean"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","value":[]},"response_processing_size_threshold":{"_input_type":"IntInput","advanced":true,"display_name":"Response Processing Size Threshold","dynamic":false,"info":"Tool output is post-processed only if response exceeds this character threshold.","list":false,"list_add_label":"Add More","name":"response_processing_size_threshold","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":100},"system_prompt":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Agent Instructions","dynamic":false,"info":"System Prompt: Initial instructions and context provided to guide the agent's behavior.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_prompt","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":"You are a helpful assistant that can use tools to answer questions and perform tasks."},"tools":{"_input_type":"HandleInput","advanced":false,"display_name":"Tools","dynamic":false,"info":"These are the tools that the agent can use to help with tasks.","input_types":["Tool"],"list":true,"list_add_label":"Add More","name":"tools","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"verbose":{"_input_type":"BoolInput","advanced":true,"display_name":"Verbose","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"verbose","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false}}],["amazon",{"AmazonBedrockConverseModel":{"base_classes":["LanguageModel","Message"],"beta":true,"conditional_paths":[],"custom_fields":{},"description":"Generate text using Amazon Bedrock LLMs with the modern Converse API for improved conversation handling.","display_name":"Amazon Bedrock Converse","documentation":"","edited":false,"field_order":["input_value","system_message","stream","model_id","aws_access_key_id","aws_secret_access_key","aws_session_token","credentials_profile_name","region_name","endpoint_url","temperature","max_tokens","top_p","top_k","disable_streaming","additional_model_fields"],"frozen":false,"icon":"Amazon","legacy":false,"metadata":{"code_hash":"54c335f8699f","dependencies":{"dependencies":[{"name":"langflow","version":null},{"name":"lfx","version":null},{"name":"langchain_aws","version":"0.2.35"}],"total_dependencies":3},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.amazon.amazon_bedrock_converse.AmazonBedrockConverseComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","additional_model_fields":{"_input_type":"DictInput","advanced":true,"display_name":"Additional Model Fields","dynamic":false,"info":"Additional model-specific parameters for fine-tuning behavior.","list":true,"list_add_label":"Add More","name":"additional_model_fields","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"aws_access_key_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Access Key ID","dynamic":false,"info":"The access key for your AWS account. Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.","input_types":[],"load_from_db":true,"name":"aws_access_key_id","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_ACCESS_KEY_ID"},"aws_secret_access_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Secret Access Key","dynamic":false,"info":"The secret key for your AWS account. Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.","input_types":[],"load_from_db":true,"name":"aws_secret_access_key","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_SECRET_ACCESS_KEY"},"aws_session_token":{"_input_type":"SecretStrInput","advanced":true,"display_name":"AWS Session Token","dynamic":false,"info":"The session key for your AWS account. Only needed for temporary credentials. Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.","input_types":[],"load_from_db":false,"name":"aws_session_token","override_skip":false,"password":true,"placeholder":"","required":false,"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":"from langflow.field_typing import LanguageModel\nfrom langflow.inputs.inputs import BoolInput, FloatInput, IntInput, MessageTextInput, SecretStrInput\nfrom langflow.io import DictInput, DropdownInput\n\nfrom lfx.base.models.aws_constants import AWS_REGIONS, AWS_MODEL_IDs\nfrom lfx.base.models.model import LCModelComponent\n\n\nclass AmazonBedrockConverseComponent(LCModelComponent):\n display_name: str = \"Amazon Bedrock Converse\"\n description: str = (\n \"Generate text using Amazon Bedrock LLMs with the modern Converse API for improved conversation handling.\"\n )\n icon = \"Amazon\"\n name = \"AmazonBedrockConverseModel\"\n beta = True\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n DropdownInput(\n name=\"model_id\",\n display_name=\"Model ID\",\n options=AWS_MODEL_IDs,\n value=\"anthropic.claude-3-5-sonnet-20241022-v2:0\",\n info=\"List of available model IDs to choose from.\",\n ),\n SecretStrInput(\n name=\"aws_access_key_id\",\n display_name=\"AWS Access Key ID\",\n info=\"The access key for your AWS account. \"\n \"Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.\",\n value=\"AWS_ACCESS_KEY_ID\",\n required=True,\n ),\n SecretStrInput(\n name=\"aws_secret_access_key\",\n display_name=\"AWS Secret Access Key\",\n info=\"The secret key for your AWS account. \"\n \"Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.\",\n value=\"AWS_SECRET_ACCESS_KEY\",\n required=True,\n ),\n SecretStrInput(\n name=\"aws_session_token\",\n display_name=\"AWS Session Token\",\n advanced=True,\n info=\"The session key for your AWS account. \"\n \"Only needed for temporary credentials. \"\n \"Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.\",\n load_from_db=False,\n ),\n SecretStrInput(\n name=\"credentials_profile_name\",\n display_name=\"Credentials Profile Name\",\n advanced=True,\n info=\"The name of the profile to use from your \"\n \"~/.aws/credentials file. \"\n \"If not provided, the default profile will be used.\",\n load_from_db=False,\n ),\n DropdownInput(\n name=\"region_name\",\n display_name=\"Region Name\",\n value=\"us-east-1\",\n options=AWS_REGIONS,\n info=\"The AWS region where your Bedrock resources are located.\",\n ),\n MessageTextInput(\n name=\"endpoint_url\",\n display_name=\"Endpoint URL\",\n advanced=True,\n info=\"The URL of the Bedrock endpoint to use.\",\n ),\n # Model-specific parameters for fine control\n FloatInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.7,\n info=\"Controls randomness in output. Higher values make output more random.\",\n advanced=True,\n ),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n value=4096,\n info=\"Maximum number of tokens to generate.\",\n advanced=True,\n ),\n FloatInput(\n name=\"top_p\",\n display_name=\"Top P\",\n value=0.9,\n info=\"Nucleus sampling parameter. Controls diversity of output.\",\n advanced=True,\n ),\n IntInput(\n name=\"top_k\",\n display_name=\"Top K\",\n value=250,\n info=\"Limits the number of highest probability vocabulary tokens to consider. \"\n \"Note: Not all models support top_k. Use 'Additional Model Fields' for manual configuration if needed.\",\n advanced=True,\n ),\n BoolInput(\n name=\"disable_streaming\",\n display_name=\"Disable Streaming\",\n value=False,\n info=\"If True, disables streaming responses. Useful for batch processing.\",\n advanced=True,\n ),\n DictInput(\n name=\"additional_model_fields\",\n display_name=\"Additional Model Fields\",\n advanced=True,\n is_list=True,\n info=\"Additional model-specific parameters for fine-tuning behavior.\",\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_aws.chat_models.bedrock_converse import ChatBedrockConverse\n except ImportError as e:\n msg = \"langchain_aws is not installed. Please install it with `pip install langchain_aws`.\"\n raise ImportError(msg) from e\n\n # Prepare initialization parameters\n init_params = {\n \"model\": self.model_id,\n \"region_name\": self.region_name,\n }\n\n # Add AWS credentials if provided\n if self.aws_access_key_id:\n init_params[\"aws_access_key_id\"] = self.aws_access_key_id\n if self.aws_secret_access_key:\n init_params[\"aws_secret_access_key\"] = self.aws_secret_access_key\n if self.aws_session_token:\n init_params[\"aws_session_token\"] = self.aws_session_token\n if self.credentials_profile_name:\n init_params[\"credentials_profile_name\"] = self.credentials_profile_name\n if self.endpoint_url:\n init_params[\"endpoint_url\"] = self.endpoint_url\n\n # Add model parameters directly as supported by ChatBedrockConverse\n if hasattr(self, \"temperature\") and self.temperature is not None:\n init_params[\"temperature\"] = self.temperature\n if hasattr(self, \"max_tokens\") and self.max_tokens is not None:\n init_params[\"max_tokens\"] = self.max_tokens\n if hasattr(self, \"top_p\") and self.top_p is not None:\n init_params[\"top_p\"] = self.top_p\n\n # Handle streaming - only disable if explicitly requested\n if hasattr(self, \"disable_streaming\") and self.disable_streaming:\n init_params[\"disable_streaming\"] = True\n\n # Handle additional model request fields carefully\n # Based on the error, inferenceConfig should not be passed as additional fields for some models\n additional_model_request_fields = {}\n\n # Only add top_k if user explicitly provided additional fields or if needed for specific models\n if hasattr(self, \"additional_model_fields\") and self.additional_model_fields:\n for field in self.additional_model_fields:\n if isinstance(field, dict):\n additional_model_request_fields.update(field)\n\n # For now, don't automatically add inferenceConfig for top_k to avoid validation errors\n # Users can manually add it via additional_model_fields if their model supports it\n\n # Only add if we have actual additional fields\n if additional_model_request_fields:\n init_params[\"additional_model_request_fields\"] = additional_model_request_fields\n\n try:\n output = ChatBedrockConverse(**init_params)\n except Exception as e:\n # Provide helpful error message with fallback suggestions\n error_details = str(e)\n if \"validation error\" in error_details.lower():\n msg = (\n f\"ChatBedrockConverse validation error: {error_details}. \"\n f\"This may be due to incompatible parameters for model '{self.model_id}'. \"\n f\"Consider adjusting the model parameters or trying the legacy Amazon Bedrock component.\"\n )\n elif \"converse api\" in error_details.lower():\n msg = (\n f\"Converse API error: {error_details}. \"\n f\"The model '{self.model_id}' may not support the Converse API. \"\n f\"Try using the legacy Amazon Bedrock component instead.\"\n )\n else:\n msg = f\"Could not initialize ChatBedrockConverse: {error_details}\"\n raise ValueError(msg) from e\n\n return output\n"},"credentials_profile_name":{"_input_type":"SecretStrInput","advanced":true,"display_name":"Credentials Profile Name","dynamic":false,"info":"The name of the profile to use from your ~/.aws/credentials file. If not provided, the default profile will be used.","input_types":[],"load_from_db":false,"name":"credentials_profile_name","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"disable_streaming":{"_input_type":"BoolInput","advanced":true,"display_name":"Disable Streaming","dynamic":false,"info":"If True, disables streaming responses. Useful for batch processing.","list":false,"list_add_label":"Add More","name":"disable_streaming","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"endpoint_url":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Endpoint URL","dynamic":false,"info":"The URL of the Bedrock endpoint to use.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"endpoint_url","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_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Max Tokens","dynamic":false,"info":"Maximum number of tokens to generate.","list":false,"list_add_label":"Add More","name":"max_tokens","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":4096},"model_id":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model ID","dynamic":false,"external_options":{},"info":"List of available model IDs to choose from.","name":"model_id","options":["amazon.titan-text-express-v1","amazon.titan-text-lite-v1","amazon.titan-text-premier-v1:0","anthropic.claude-v2","anthropic.claude-v2:1","anthropic.claude-3-sonnet-20240229-v1:0","anthropic.claude-3-5-sonnet-20240620-v1:0","anthropic.claude-3-5-sonnet-20241022-v2:0","anthropic.claude-3-haiku-20240307-v1:0","anthropic.claude-3-5-haiku-20241022-v1:0","anthropic.claude-3-opus-20240229-v1:0","anthropic.claude-instant-v1","ai21.jamba-instruct-v1:0","ai21.j2-mid-v1","ai21.j2-ultra-v1","ai21.jamba-1-5-large-v1:0","ai21.jamba-1-5-mini-v1:0","cohere.command-text-v14","cohere.command-light-text-v14","cohere.command-r-v1:0","cohere.command-r-plus-v1:0","meta.llama2-13b-chat-v1","meta.llama2-70b-chat-v1","meta.llama3-8b-instruct-v1:0","meta.llama3-70b-instruct-v1:0","meta.llama3-1-8b-instruct-v1:0","meta.llama3-1-70b-instruct-v1:0","meta.llama3-1-405b-instruct-v1:0","meta.llama3-2-1b-instruct-v1:0","meta.llama3-2-3b-instruct-v1:0","meta.llama3-2-11b-instruct-v1:0","meta.llama3-2-90b-instruct-v1:0","mistral.mistral-7b-instruct-v0:2","mistral.mixtral-8x7b-instruct-v0:1","mistral.mistral-large-2402-v1:0","mistral.mistral-large-2407-v1:0","mistral.mistral-small-2402-v1:0"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"anthropic.claude-3-5-sonnet-20241022-v2:0"},"region_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region Name","dynamic":false,"external_options":{},"info":"The AWS region where your Bedrock resources are located.","name":"region_name","options":["us-west-2","us-west-1","us-gov-west-1","us-gov-east-1","us-east-2","us-east-1","sa-east-1","me-south-1","me-central-1","il-central-1","eu-west-3","eu-west-2","eu-west-1","eu-south-2","eu-south-1","eu-north-1","eu-central-2","eu-central-1","cn-northwest-1","cn-north-1","ca-west-1","ca-central-1","ap-southeast-5","ap-southeast-4","ap-southeast-3","ap-southeast-2","ap-southeast-1","ap-south-2","ap-south-1","ap-northeast-3","ap-northeast-2","ap-northeast-1","ap-east-1","af-south-1"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"us-east-1"},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"FloatInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"Controls randomness in output. Higher values make output more random.","list":false,"list_add_label":"Add More","name":"temperature","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.7},"top_k":{"_input_type":"IntInput","advanced":true,"display_name":"Top K","dynamic":false,"info":"Limits the number of highest probability vocabulary tokens to consider. Note: Not all models support top_k. Use 'Additional Model Fields' for manual configuration if needed.","list":false,"list_add_label":"Add More","name":"top_k","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":250},"top_p":{"_input_type":"FloatInput","advanced":true,"display_name":"Top P","dynamic":false,"info":"Nucleus sampling parameter. Controls diversity of output.","list":false,"list_add_label":"Add More","name":"top_p","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.9}},"tool_mode":false},"AmazonBedrockEmbeddings":{"base_classes":["Embeddings"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate embeddings using Amazon Bedrock models.","display_name":"Amazon Bedrock Embeddings","documentation":"","edited":false,"field_order":["model_id","aws_access_key_id","aws_secret_access_key","aws_session_token","credentials_profile_name","region_name","endpoint_url"],"frozen":false,"icon":"Amazon","legacy":false,"metadata":{"code_hash":"70d039ff79f0","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_aws","version":"0.2.35"},{"name":"boto3","version":"1.40.61"}],"total_dependencies":3},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.amazon.amazon_bedrock_embedding.AmazonBedrockEmbeddingsComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Embeddings","group_outputs":false,"method":"build_embeddings","name":"embeddings","selected":"Embeddings","tool_mode":true,"types":["Embeddings"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","aws_access_key_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Access Key ID","dynamic":false,"info":"The access key for your AWS account.Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.","input_types":[],"load_from_db":true,"name":"aws_access_key_id","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_ACCESS_KEY_ID"},"aws_secret_access_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Secret Access Key","dynamic":false,"info":"The secret key for your AWS account. Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.","input_types":[],"load_from_db":true,"name":"aws_secret_access_key","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_SECRET_ACCESS_KEY"},"aws_session_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Session Token","dynamic":false,"info":"The session key for your AWS account. Only needed for temporary credentials. Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.","input_types":[],"load_from_db":true,"name":"aws_session_token","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_SESSION_TOKEN"},"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":"from lfx.base.models.aws_constants import AWS_EMBEDDING_MODEL_IDS, AWS_REGIONS\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import Embeddings\nfrom lfx.inputs.inputs import SecretStrInput\nfrom lfx.io import DropdownInput, MessageTextInput, Output\n\n\nclass AmazonBedrockEmbeddingsComponent(LCModelComponent):\n display_name: str = \"Amazon Bedrock Embeddings\"\n description: str = \"Generate embeddings using Amazon Bedrock models.\"\n icon = \"Amazon\"\n name = \"AmazonBedrockEmbeddings\"\n\n inputs = [\n DropdownInput(\n name=\"model_id\",\n display_name=\"Model Id\",\n options=AWS_EMBEDDING_MODEL_IDS,\n value=\"amazon.titan-embed-text-v1\",\n ),\n SecretStrInput(\n name=\"aws_access_key_id\",\n display_name=\"AWS Access Key ID\",\n info=\"The access key for your AWS account.\"\n \"Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.\",\n value=\"AWS_ACCESS_KEY_ID\",\n required=True,\n ),\n SecretStrInput(\n name=\"aws_secret_access_key\",\n display_name=\"AWS Secret Access Key\",\n info=\"The secret key for your AWS account. \"\n \"Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.\",\n value=\"AWS_SECRET_ACCESS_KEY\",\n required=True,\n ),\n SecretStrInput(\n name=\"aws_session_token\",\n display_name=\"AWS Session Token\",\n advanced=False,\n info=\"The session key for your AWS account. \"\n \"Only needed for temporary credentials. \"\n \"Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.\",\n value=\"AWS_SESSION_TOKEN\",\n ),\n SecretStrInput(\n name=\"credentials_profile_name\",\n display_name=\"Credentials Profile Name\",\n advanced=True,\n info=\"The name of the profile to use from your \"\n \"~/.aws/credentials file. \"\n \"If not provided, the default profile will be used.\",\n value=\"AWS_CREDENTIALS_PROFILE_NAME\",\n ),\n DropdownInput(\n name=\"region_name\",\n display_name=\"Region Name\",\n value=\"us-east-1\",\n options=AWS_REGIONS,\n info=\"The AWS region where your Bedrock resources are located.\",\n ),\n MessageTextInput(\n name=\"endpoint_url\",\n display_name=\"Endpoint URL\",\n advanced=True,\n info=\"The URL of the AWS Bedrock endpoint to use.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n try:\n from langchain_aws import BedrockEmbeddings\n except ImportError as e:\n msg = \"langchain_aws is not installed. Please install it with `pip install langchain_aws`.\"\n raise ImportError(msg) from e\n try:\n import boto3\n except ImportError as e:\n msg = \"boto3 is not installed. Please install it with `pip install boto3`.\"\n raise ImportError(msg) from e\n if self.aws_access_key_id or self.aws_secret_access_key:\n session = boto3.Session(\n aws_access_key_id=self.aws_access_key_id,\n aws_secret_access_key=self.aws_secret_access_key,\n aws_session_token=self.aws_session_token,\n )\n elif self.credentials_profile_name:\n session = boto3.Session(profile_name=self.credentials_profile_name)\n else:\n session = boto3.Session()\n\n client_params = {}\n if self.endpoint_url:\n client_params[\"endpoint_url\"] = self.endpoint_url\n if self.region_name:\n client_params[\"region_name\"] = self.region_name\n\n boto3_client = session.client(\"bedrock-runtime\", **client_params)\n return BedrockEmbeddings(\n credentials_profile_name=self.credentials_profile_name,\n client=boto3_client,\n model_id=self.model_id,\n endpoint_url=self.endpoint_url,\n region_name=self.region_name,\n )\n"},"credentials_profile_name":{"_input_type":"SecretStrInput","advanced":true,"display_name":"Credentials Profile Name","dynamic":false,"info":"The name of the profile to use from your ~/.aws/credentials file. If not provided, the default profile will be used.","input_types":[],"load_from_db":true,"name":"credentials_profile_name","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_CREDENTIALS_PROFILE_NAME"},"endpoint_url":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Endpoint URL","dynamic":false,"info":"The URL of the AWS Bedrock endpoint to use.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"endpoint_url","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":""},"model_id":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model Id","dynamic":false,"external_options":{},"info":"","name":"model_id","options":["amazon.titan-embed-text-v1","amazon.titan-embed-text-v2:0","cohere.embed-english-v3","cohere.embed-multilingual-v3"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"amazon.titan-embed-text-v1"},"region_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region Name","dynamic":false,"external_options":{},"info":"The AWS region where your Bedrock resources are located.","name":"region_name","options":["us-west-2","us-west-1","us-gov-west-1","us-gov-east-1","us-east-2","us-east-1","sa-east-1","me-south-1","me-central-1","il-central-1","eu-west-3","eu-west-2","eu-west-1","eu-south-2","eu-south-1","eu-north-1","eu-central-2","eu-central-1","cn-northwest-1","cn-north-1","ca-west-1","ca-central-1","ap-southeast-5","ap-southeast-4","ap-southeast-3","ap-southeast-2","ap-southeast-1","ap-south-2","ap-south-1","ap-northeast-3","ap-northeast-2","ap-northeast-1","ap-east-1","af-south-1"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"us-east-1"}},"tool_mode":false},"AmazonBedrockModel":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate text using Amazon Bedrock LLMs with the legacy ChatBedrock API. This component is deprecated. Please use Amazon Bedrock Converse instead for better compatibility, newer features, and improved conversation handling.","display_name":"Amazon Bedrock","documentation":"","edited":false,"field_order":["input_value","system_message","stream","model_id","aws_access_key_id","aws_secret_access_key","aws_session_token","credentials_profile_name","region_name","model_kwargs","endpoint_url"],"frozen":false,"icon":"Amazon","legacy":true,"metadata":{"code_hash":"922093a831b6","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_aws","version":"0.2.35"},{"name":"boto3","version":"1.40.61"}],"total_dependencies":3},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.amazon.amazon_bedrock_model.AmazonBedrockComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","aws_access_key_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Access Key ID","dynamic":false,"info":"The access key for your AWS account.Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.","input_types":[],"load_from_db":true,"name":"aws_access_key_id","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_ACCESS_KEY_ID"},"aws_secret_access_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Secret Access Key","dynamic":false,"info":"The secret key for your AWS account. Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.","input_types":[],"load_from_db":true,"name":"aws_secret_access_key","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"AWS_SECRET_ACCESS_KEY"},"aws_session_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Session Token","dynamic":false,"info":"The session key for your AWS account. Only needed for temporary credentials. Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.","input_types":[],"load_from_db":false,"name":"aws_session_token","override_skip":false,"password":true,"placeholder":"","required":false,"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":"from lfx.base.models.aws_constants import AWS_REGIONS, AWS_MODEL_IDs\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.inputs.inputs import MessageTextInput, SecretStrInput\nfrom lfx.io import DictInput, DropdownInput\n\n\nclass AmazonBedrockComponent(LCModelComponent):\n display_name: str = \"Amazon Bedrock\"\n description: str = (\n \"Generate text using Amazon Bedrock LLMs with the legacy ChatBedrock API. \"\n \"This component is deprecated. Please use Amazon Bedrock Converse instead \"\n \"for better compatibility, newer features, and improved conversation handling.\"\n )\n icon = \"Amazon\"\n name = \"AmazonBedrockModel\"\n legacy = True\n replacement = \"amazon.AmazonBedrockConverseModel\"\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n DropdownInput(\n name=\"model_id\",\n display_name=\"Model ID\",\n options=AWS_MODEL_IDs,\n value=\"anthropic.claude-3-haiku-20240307-v1:0\",\n info=\"List of available model IDs to choose from.\",\n ),\n SecretStrInput(\n name=\"aws_access_key_id\",\n display_name=\"AWS Access Key ID\",\n info=\"The access key for your AWS account.\"\n \"Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.\",\n value=\"AWS_ACCESS_KEY_ID\",\n required=True,\n ),\n SecretStrInput(\n name=\"aws_secret_access_key\",\n display_name=\"AWS Secret Access Key\",\n info=\"The secret key for your AWS account. \"\n \"Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.\",\n value=\"AWS_SECRET_ACCESS_KEY\",\n required=True,\n ),\n SecretStrInput(\n name=\"aws_session_token\",\n display_name=\"AWS Session Token\",\n advanced=False,\n info=\"The session key for your AWS account. \"\n \"Only needed for temporary credentials. \"\n \"Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.\",\n load_from_db=False,\n ),\n SecretStrInput(\n name=\"credentials_profile_name\",\n display_name=\"Credentials Profile Name\",\n advanced=True,\n info=\"The name of the profile to use from your \"\n \"~/.aws/credentials file. \"\n \"If not provided, the default profile will be used.\",\n load_from_db=False,\n ),\n DropdownInput(\n name=\"region_name\",\n display_name=\"Region Name\",\n value=\"us-east-1\",\n options=AWS_REGIONS,\n info=\"The AWS region where your Bedrock resources are located.\",\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n is_list=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n MessageTextInput(\n name=\"endpoint_url\",\n display_name=\"Endpoint URL\",\n advanced=True,\n info=\"The URL of the Bedrock endpoint to use.\",\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_aws import ChatBedrock\n except ImportError as e:\n msg = \"langchain_aws is not installed. Please install it with `pip install langchain_aws`.\"\n raise ImportError(msg) from e\n try:\n import boto3\n except ImportError as e:\n msg = \"boto3 is not installed. Please install it with `pip install boto3`.\"\n raise ImportError(msg) from e\n if self.aws_access_key_id or self.aws_secret_access_key:\n try:\n session = boto3.Session(\n aws_access_key_id=self.aws_access_key_id,\n aws_secret_access_key=self.aws_secret_access_key,\n aws_session_token=self.aws_session_token,\n )\n except Exception as e:\n msg = \"Could not create a boto3 session.\"\n raise ValueError(msg) from e\n elif self.credentials_profile_name:\n session = boto3.Session(profile_name=self.credentials_profile_name)\n else:\n session = boto3.Session()\n\n client_params = {}\n if self.endpoint_url:\n client_params[\"endpoint_url\"] = self.endpoint_url\n if self.region_name:\n client_params[\"region_name\"] = self.region_name\n\n boto3_client = session.client(\"bedrock-runtime\", **client_params)\n try:\n output = ChatBedrock(\n client=boto3_client,\n model_id=self.model_id,\n region_name=self.region_name,\n model_kwargs=self.model_kwargs,\n endpoint_url=self.endpoint_url,\n streaming=self.stream,\n )\n except Exception as e:\n msg = \"Could not connect to AmazonBedrock API.\"\n raise ValueError(msg) from e\n return output\n"},"credentials_profile_name":{"_input_type":"SecretStrInput","advanced":true,"display_name":"Credentials Profile Name","dynamic":false,"info":"The name of the profile to use from your ~/.aws/credentials file. If not provided, the default profile will be used.","input_types":[],"load_from_db":false,"name":"credentials_profile_name","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"endpoint_url":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Endpoint URL","dynamic":false,"info":"The URL of the Bedrock endpoint to use.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"endpoint_url","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_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"model_id":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model ID","dynamic":false,"external_options":{},"info":"List of available model IDs to choose from.","name":"model_id","options":["amazon.titan-text-express-v1","amazon.titan-text-lite-v1","amazon.titan-text-premier-v1:0","anthropic.claude-v2","anthropic.claude-v2:1","anthropic.claude-3-sonnet-20240229-v1:0","anthropic.claude-3-5-sonnet-20240620-v1:0","anthropic.claude-3-5-sonnet-20241022-v2:0","anthropic.claude-3-haiku-20240307-v1:0","anthropic.claude-3-5-haiku-20241022-v1:0","anthropic.claude-3-opus-20240229-v1:0","anthropic.claude-instant-v1","ai21.jamba-instruct-v1:0","ai21.j2-mid-v1","ai21.j2-ultra-v1","ai21.jamba-1-5-large-v1:0","ai21.jamba-1-5-mini-v1:0","cohere.command-text-v14","cohere.command-light-text-v14","cohere.command-r-v1:0","cohere.command-r-plus-v1:0","meta.llama2-13b-chat-v1","meta.llama2-70b-chat-v1","meta.llama3-8b-instruct-v1:0","meta.llama3-70b-instruct-v1:0","meta.llama3-1-8b-instruct-v1:0","meta.llama3-1-70b-instruct-v1:0","meta.llama3-1-405b-instruct-v1:0","meta.llama3-2-1b-instruct-v1:0","meta.llama3-2-3b-instruct-v1:0","meta.llama3-2-11b-instruct-v1:0","meta.llama3-2-90b-instruct-v1:0","mistral.mistral-7b-instruct-v0:2","mistral.mixtral-8x7b-instruct-v0:1","mistral.mistral-large-2402-v1:0","mistral.mistral-large-2407-v1:0","mistral.mistral-small-2402-v1:0"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"anthropic.claude-3-haiku-20240307-v1:0"},"model_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Model Kwargs","dynamic":false,"info":"Additional keyword arguments to pass to the model.","list":true,"list_add_label":"Add More","name":"model_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"region_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region Name","dynamic":false,"external_options":{},"info":"The AWS region where your Bedrock resources are located.","name":"region_name","options":["us-west-2","us-west-1","us-gov-west-1","us-gov-east-1","us-east-2","us-east-1","sa-east-1","me-south-1","me-central-1","il-central-1","eu-west-3","eu-west-2","eu-west-1","eu-south-2","eu-south-1","eu-north-1","eu-central-2","eu-central-1","cn-northwest-1","cn-north-1","ca-west-1","ca-central-1","ap-southeast-5","ap-southeast-4","ap-southeast-3","ap-southeast-2","ap-southeast-1","ap-south-2","ap-south-1","ap-northeast-3","ap-northeast-2","ap-northeast-1","ap-east-1","af-south-1"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"us-east-1"},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""}},"tool_mode":false},"s3bucketuploader":{"base_classes":["NoneType"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Uploads files to S3 bucket.","display_name":"S3 Bucket Uploader","documentation":"","edited":false,"field_order":["aws_access_key_id","aws_secret_access_key","bucket_name","strategy","data_inputs","s3_prefix","strip_path"],"frozen":false,"icon":"Amazon","legacy":false,"metadata":{"code_hash":"6e4ba2dafc3c","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"boto3","version":"1.40.61"}],"total_dependencies":2},"module":"lfx.components.amazon.s3_bucket_uploader.S3BucketUploaderComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Writes to AWS Bucket","group_outputs":false,"method":"process_files","name":"data","selected":"NoneType","tool_mode":true,"types":["NoneType"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","aws_access_key_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Access Key ID","dynamic":false,"info":"AWS Access key ID.","input_types":[],"load_from_db":true,"name":"aws_access_key_id","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"aws_secret_access_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"AWS Secret Key","dynamic":false,"info":"AWS Secret Key.","input_types":[],"load_from_db":true,"name":"aws_secret_access_key","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"bucket_name":{"_input_type":"StrInput","advanced":false,"display_name":"Bucket Name","dynamic":false,"info":"Enter the name of the bucket.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"bucket_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"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":"from pathlib import Path\nfrom typing import Any\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import (\n BoolInput,\n DropdownInput,\n HandleInput,\n Output,\n SecretStrInput,\n StrInput,\n)\n\n\nclass S3BucketUploaderComponent(Component):\n \"\"\"S3BucketUploaderComponent is a component responsible for uploading files to an S3 bucket.\n\n It provides two strategies for file upload: \"By Data\" and \"By File Name\". The component\n requires AWS credentials and bucket details as inputs and processes files accordingly.\n\n Attributes:\n display_name (str): The display name of the component.\n description (str): A brief description of the components functionality.\n icon (str): The icon representing the component.\n name (str): The internal name of the component.\n inputs (list): A list of input configurations required by the component.\n outputs (list): A list of output configurations provided by the component.\n\n Methods:\n process_files() -> None:\n Processes files based on the selected strategy. Calls the appropriate method\n based on the strategy attribute.\n process_files_by_data() -> None:\n Processes and uploads files to an S3 bucket based on the data inputs. Iterates\n over the data inputs, logs the file path and text content, and uploads each file\n to the specified S3 bucket if both file path and text content are available.\n process_files_by_name() -> None:\n Processes and uploads files to an S3 bucket based on their names. Iterates through\n the list of data inputs, retrieves the file path from each data item, and uploads\n the file to the specified S3 bucket if the file path is available. Logs the file\n path being uploaded.\n _s3_client() -> Any:\n Creates and returns an S3 client using the provided AWS access key ID and secret\n access key.\n\n Please note that this component requires the boto3 library to be installed. It is designed\n to work with File and Director components as inputs\n \"\"\"\n\n display_name = \"S3 Bucket Uploader\"\n description = \"Uploads files to S3 bucket.\"\n icon = \"Amazon\"\n name = \"s3bucketuploader\"\n\n inputs = [\n SecretStrInput(\n name=\"aws_access_key_id\",\n display_name=\"AWS Access Key ID\",\n required=True,\n password=True,\n info=\"AWS Access key ID.\",\n ),\n SecretStrInput(\n name=\"aws_secret_access_key\",\n display_name=\"AWS Secret Key\",\n required=True,\n password=True,\n info=\"AWS Secret Key.\",\n ),\n StrInput(\n name=\"bucket_name\",\n display_name=\"Bucket Name\",\n info=\"Enter the name of the bucket.\",\n advanced=False,\n ),\n DropdownInput(\n name=\"strategy\",\n display_name=\"Strategy for file upload\",\n options=[\"Store Data\", \"Store Original File\"],\n value=\"By Data\",\n info=(\n \"Choose the strategy to upload the file. By Data means that the source file \"\n \"is parsed and stored as LangFlow data. By File Name means that the source \"\n \"file is uploaded as is.\"\n ),\n ),\n HandleInput(\n name=\"data_inputs\",\n display_name=\"Data Inputs\",\n info=\"The data to split.\",\n input_types=[\"Data\"],\n is_list=True,\n required=True,\n ),\n StrInput(\n name=\"s3_prefix\",\n display_name=\"S3 Prefix\",\n info=\"Prefix for all files.\",\n advanced=True,\n ),\n BoolInput(\n name=\"strip_path\",\n display_name=\"Strip Path\",\n info=\"Removes path from file path.\",\n required=True,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Writes to AWS Bucket\", name=\"data\", method=\"process_files\"),\n ]\n\n def process_files(self) -> None:\n \"\"\"Process files based on the selected strategy.\n\n This method uses a strategy pattern to process files. The strategy is determined\n by the `self.strategy` attribute, which can be either \"By Data\" or \"By File Name\".\n Depending on the strategy, the corresponding method (`process_files_by_data` or\n `process_files_by_name`) is called. If an invalid strategy is provided, an error\n is logged.\n\n Returns:\n None\n \"\"\"\n strategy_methods = {\n \"Store Data\": self.process_files_by_data,\n \"Store Original File\": self.process_files_by_name,\n }\n strategy_methods.get(self.strategy, lambda: self.log(\"Invalid strategy\"))()\n\n def process_files_by_data(self) -> None:\n \"\"\"Processes and uploads files to an S3 bucket based on the data inputs.\n\n This method iterates over the data inputs, logs the file path and text content,\n and uploads each file to the specified S3 bucket if both file path and text content\n are available.\n\n Args:\n None\n\n Returns:\n None\n \"\"\"\n for data_item in self.data_inputs:\n file_path = data_item.data.get(\"file_path\")\n text_content = data_item.data.get(\"text\")\n\n if file_path and text_content:\n self._s3_client().put_object(\n Bucket=self.bucket_name, Key=self._normalize_path(file_path), Body=text_content\n )\n\n def process_files_by_name(self) -> None:\n \"\"\"Processes and uploads files to an S3 bucket based on their names.\n\n Iterates through the list of data inputs, retrieves the file path from each data item,\n and uploads the file to the specified S3 bucket if the file path is available.\n Logs the file path being uploaded.\n\n Returns:\n None\n \"\"\"\n for data_item in self.data_inputs:\n file_path = data_item.data.get(\"file_path\")\n self.log(f\"Uploading file: {file_path}\")\n if file_path:\n self._s3_client().upload_file(file_path, Bucket=self.bucket_name, Key=self._normalize_path(file_path))\n\n def _s3_client(self) -> Any:\n \"\"\"Creates and returns an S3 client using the provided AWS access key ID and secret access key.\n\n Returns:\n Any: A boto3 S3 client instance.\n \"\"\"\n try:\n import boto3\n except ImportError as e:\n msg = \"boto3 is not installed. Please install it using `uv pip install boto3`.\"\n raise ImportError(msg) from e\n\n return boto3.client(\n \"s3\",\n aws_access_key_id=self.aws_access_key_id,\n aws_secret_access_key=self.aws_secret_access_key,\n )\n\n def _normalize_path(self, file_path) -> str:\n \"\"\"Process the file path based on the s3_prefix and path_as_prefix.\n\n Args:\n file_path (str): The original file path.\n s3_prefix (str): The S3 prefix to use.\n path_as_prefix (bool): Whether to use the file path as the S3 prefix.\n\n Returns:\n str: The processed file path.\n \"\"\"\n prefix = self.s3_prefix\n strip_path = self.strip_path\n processed_path: str = file_path\n\n if strip_path:\n # Filename only\n processed_path = Path(file_path).name\n\n # Concatenate the s3_prefix if it exists\n if prefix:\n processed_path = str(Path(prefix) / processed_path)\n\n return processed_path\n"},"data_inputs":{"_input_type":"HandleInput","advanced":false,"display_name":"Data Inputs","dynamic":false,"info":"The data to split.","input_types":["Data"],"list":true,"list_add_label":"Add More","name":"data_inputs","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"s3_prefix":{"_input_type":"StrInput","advanced":true,"display_name":"S3 Prefix","dynamic":false,"info":"Prefix for all files.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"s3_prefix","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"strategy":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Strategy for file upload","dynamic":false,"external_options":{},"info":"Choose the strategy to upload the file. By Data means that the source file is parsed and stored as LangFlow data. By File Name means that the source file is uploaded as is.","name":"strategy","options":["Store Data","Store Original File"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"By Data"},"strip_path":{"_input_type":"BoolInput","advanced":true,"display_name":"Strip Path","dynamic":false,"info":"Removes path from file path.","list":false,"list_add_label":"Add More","name":"strip_path","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false}}],["anthropic",{"AnthropicModel":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate text using Anthropic's Messages API and models.","display_name":"Anthropic","documentation":"","edited":false,"field_order":["input_value","system_message","stream","max_tokens","model_name","api_key","temperature","base_url","tool_model_enabled"],"frozen":false,"icon":"Anthropic","legacy":false,"metadata":{"code_hash":"7c894c5a66ba","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null},{"name":"langchain_anthropic","version":"0.3.14"},{"name":"anthropic","version":"0.75.0"}],"total_dependencies":5},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.anthropic.anthropic.AnthropicModelComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Anthropic API Key","dynamic":false,"info":"Your Anthropic API key.","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str"},"base_url":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Anthropic API URL","dynamic":false,"info":"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"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":"https://api.anthropic.com"},"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":"from typing import Any, cast\n\nimport requests\nfrom pydantic import ValidationError\n\nfrom lfx.base.models.anthropic_constants import (\n ANTHROPIC_MODELS,\n DEFAULT_ANTHROPIC_API_URL,\n TOOL_CALLING_SUPPORTED_ANTHROPIC_MODELS,\n TOOL_CALLING_UNSUPPORTED_ANTHROPIC_MODELS,\n)\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\n\n\nclass AnthropicModelComponent(LCModelComponent):\n display_name = \"Anthropic\"\n description = \"Generate text using Anthropic's Messages API and models.\"\n icon = \"Anthropic\"\n name = \"AnthropicModel\"\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n value=4096,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=ANTHROPIC_MODELS,\n refresh_button=True,\n value=ANTHROPIC_MODELS[0],\n combobox=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Anthropic API Key\",\n info=\"Your Anthropic API key.\",\n value=None,\n required=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Anthropic API URL\",\n info=\"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.\",\n value=DEFAULT_ANTHROPIC_API_URL,\n real_time_refresh=True,\n advanced=True,\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Enable Tool Models\",\n info=(\n \"Select if you want to use models that can work with tools. If yes, only those models will be shown.\"\n ),\n advanced=False,\n value=False,\n real_time_refresh=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n try:\n max_tokens_value = getattr(self, \"max_tokens\", \"\")\n max_tokens_value = 4096 if max_tokens_value == \"\" else int(max_tokens_value)\n output = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n max_tokens=max_tokens_value,\n temperature=self.temperature,\n anthropic_api_url=self.base_url or DEFAULT_ANTHROPIC_API_URL,\n streaming=self.stream,\n )\n except ValidationError:\n raise\n except Exception as e:\n msg = \"Could not connect to Anthropic API.\"\n raise ValueError(msg) from e\n\n return output\n\n def get_models(self, *, tool_model_enabled: bool | None = None) -> list[str]:\n try:\n import anthropic\n\n client = anthropic.Anthropic(api_key=self.api_key)\n models = client.models.list(limit=20).data\n model_ids = ANTHROPIC_MODELS + [model.id for model in models]\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n model_ids = ANTHROPIC_MODELS\n\n if tool_model_enabled:\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n\n # Create a new list instead of modifying while iterating\n filtered_models = []\n for model in model_ids:\n if model in TOOL_CALLING_SUPPORTED_ANTHROPIC_MODELS:\n filtered_models.append(model)\n continue\n\n model_with_tool = ChatAnthropic(\n model=model, # Use the current model being checked\n anthropic_api_key=self.api_key,\n anthropic_api_url=cast(\"str\", self.base_url) or DEFAULT_ANTHROPIC_API_URL,\n )\n\n if (\n not self.supports_tool_calling(model_with_tool)\n or model in TOOL_CALLING_UNSUPPORTED_ANTHROPIC_MODELS\n ):\n continue\n\n filtered_models.append(model)\n\n return filtered_models\n\n return model_ids\n\n def _get_exception_message(self, exception: Exception) -> str | None:\n \"\"\"Get a message from an Anthropic exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from anthropic import BadRequestError\n except ImportError:\n return None\n if isinstance(exception, BadRequestError):\n message = exception.body.get(\"error\", {}).get(\"message\")\n if message:\n return message\n return None\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):\n if \"base_url\" in build_config and build_config[\"base_url\"][\"value\"] is None:\n build_config[\"base_url\"][\"value\"] = DEFAULT_ANTHROPIC_API_URL\n self.base_url = DEFAULT_ANTHROPIC_API_URL\n if field_name in {\"base_url\", \"model_name\", \"tool_model_enabled\", \"api_key\"} and field_value:\n try:\n if len(self.api_key) == 0:\n ids = ANTHROPIC_MODELS\n else:\n try:\n ids = self.get_models(tool_model_enabled=self.tool_model_enabled)\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n ids = ANTHROPIC_MODELS\n build_config.setdefault(\"model_name\", {})\n build_config[\"model_name\"][\"options\"] = ids\n build_config[\"model_name\"].setdefault(\"value\", ids[0])\n build_config[\"model_name\"][\"combobox\"] = True\n except Exception as e:\n msg = f\"Error getting model names: {e}\"\n raise ValueError(msg) from e\n return build_config\n"},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Max Tokens","dynamic":false,"info":"The maximum number of tokens to generate. Set to 0 for unlimited tokens.","list":false,"list_add_label":"Add More","name":"max_tokens","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":4096},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{},"display_name":"Model Name","dynamic":false,"external_options":{},"info":"","name":"model_name","options":["claude-opus-4-5-20251101","claude-haiku-4-5-20251001","claude-sonnet-4-5-20250929","claude-opus-4-1-20250805","claude-opus-4-20250514","claude-sonnet-4-20250514","claude-3-5-haiku-20241022","claude-3-haiku-20240307"],"options_metadata":[],"override_skip":false,"placeholder":"","refresh_button":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"claude-opus-4-5-20251101"},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"SliderInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].","max_label":"","max_label_icon":"","min_label":"","min_label_icon":"","name":"temperature","override_skip":false,"placeholder":"","range_spec":{"max":1.0,"min":0.0,"step":0.01,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":0.1},"tool_model_enabled":{"_input_type":"BoolInput","advanced":false,"display_name":"Enable Tool Models","dynamic":false,"info":"Select if you want to use models that can work with tools. If yes, only those models will be shown.","list":false,"list_add_label":"Add More","name":"tool_model_enabled","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false}}],["apify",{"ApifyActors":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Use Apify Actors to extract data from hundreds of places fast. This component can be used in a flow to retrieve data or as a tool with an agent.","display_name":"Apify Actors","documentation":"https://docs.langflow.org/bundles-apify","edited":false,"field_order":["apify_token","actor_id","run_input","dataset_fields","flatten_dataset"],"frozen":false,"icon":"Apify","legacy":false,"metadata":{"code_hash":"e84290d462c2","dependencies":{"dependencies":[{"name":"apify_client","version":"1.12.2"},{"name":"langchain_community","version":"0.3.21"},{"name":"langchain_core","version":"0.3.80"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":5},"module":"lfx.components.apify.apify_actor.ApifyActorsComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Output","group_outputs":false,"method":"run_model","name":"output","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","actor_id":{"_input_type":"StrInput","advanced":false,"display_name":"Actor","dynamic":false,"info":"Actor name from Apify store to run. For example 'apify/website-content-crawler' to use the Website Content Crawler Actor.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"actor_id","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"apify/website-content-crawler"},"apify_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Apify Token","dynamic":false,"info":"The API token for the Apify account.","input_types":[],"load_from_db":true,"name":"apify_token","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 string\nfrom typing import Any, cast\n\nfrom apify_client import ApifyClient\nfrom langchain_community.document_loaders.apify_dataset import ApifyDatasetLoader\nfrom langchain_core.tools import BaseTool\nfrom pydantic import BaseModel, Field, field_serializer\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import BoolInput\nfrom lfx.io import MultilineInput, Output, SecretStrInput, StrInput\nfrom lfx.schema.data import Data\n\nMAX_DESCRIPTION_LEN = 250\n\n\nclass ApifyActorsComponent(Component):\n display_name = \"Apify Actors\"\n description = (\n \"Use Apify Actors to extract data from hundreds of places fast. \"\n \"This component can be used in a flow to retrieve data or as a tool with an agent.\"\n )\n documentation: str = \"https://docs.langflow.org/bundles-apify\"\n icon = \"Apify\"\n name = \"ApifyActors\"\n\n inputs = [\n SecretStrInput(\n name=\"apify_token\",\n display_name=\"Apify Token\",\n info=\"The API token for the Apify account.\",\n required=True,\n password=True,\n ),\n StrInput(\n name=\"actor_id\",\n display_name=\"Actor\",\n info=(\n \"Actor name from Apify store to run. For example 'apify/website-content-crawler' \"\n \"to use the Website Content Crawler Actor.\"\n ),\n value=\"apify/website-content-crawler\",\n required=True,\n ),\n # multiline input is more pleasant to use than the nested dict input\n MultilineInput(\n name=\"run_input\",\n display_name=\"Run input\",\n info=(\n 'The JSON input for the Actor run. For example for the \"apify/website-content-crawler\" Actor: '\n '{\"startUrls\":[{\"url\":\"https://docs.apify.com/academy/web-scraping-for-beginners\"}],\"maxCrawlDepth\":0}'\n ),\n value='{\"startUrls\":[{\"url\":\"https://docs.apify.com/academy/web-scraping-for-beginners\"}],\"maxCrawlDepth\":0}',\n required=True,\n ),\n MultilineInput(\n name=\"dataset_fields\",\n display_name=\"Output fields\",\n info=(\n \"Fields to extract from the dataset, split by commas. \"\n \"Other fields will be ignored. Dots in nested structures will be replaced by underscores. \"\n \"Sample input: 'text, metadata.title'. \"\n \"Sample output: {'text': 'page content here', 'metadata_title': 'page title here'}. \"\n \"For example, for the 'apify/website-content-crawler' Actor, you can extract the 'markdown' field, \"\n \"which is the content of the website in markdown format.\"\n ),\n ),\n BoolInput(\n name=\"flatten_dataset\",\n display_name=\"Flatten output\",\n info=(\n \"The output dataset will be converted from a nested format to a flat structure. \"\n \"Dots in nested structure will be replaced by underscores. \"\n \"This is useful for further processing of the Data object. \"\n \"For example, {'a': {'b': 1}} will be flattened to {'a_b': 1}.\"\n ),\n ),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", type_=list[Data], method=\"run_model\"),\n Output(display_name=\"Tool\", name=\"tool\", type_=Tool, method=\"build_tool\"),\n ]\n\n def __init__(self, *args, **kwargs) -> None:\n super().__init__(*args, **kwargs)\n self._apify_client: ApifyClient | None = None\n\n def run_model(self) -> list[Data]:\n \"\"\"Run the Actor and return node output.\"\"\"\n input_ = json.loads(self.run_input)\n fields = ApifyActorsComponent.parse_dataset_fields(self.dataset_fields) if self.dataset_fields else None\n res = self.run_actor(self.actor_id, input_, fields=fields)\n if self.flatten_dataset:\n res = [ApifyActorsComponent.flatten(item) for item in res]\n data = [Data(data=item) for item in res]\n\n self.status = data\n return data\n\n def build_tool(self) -> Tool:\n \"\"\"Build a tool for an agent that runs the Apify Actor.\"\"\"\n actor_id = self.actor_id\n\n build = self._get_actor_latest_build(actor_id)\n readme = build.get(\"readme\", \"\")[:250] + \"...\"\n if not (input_schema_str := build.get(\"inputSchema\")):\n msg = \"Input schema not found\"\n raise ValueError(msg)\n input_schema = json.loads(input_schema_str)\n properties, required = ApifyActorsComponent.get_actor_input_schema_from_build(input_schema)\n properties = {\"run_input\": properties}\n\n # works from input schema\n info_ = [\n (\n \"JSON encoded as a string with input schema (STRICTLY FOLLOW JSON FORMAT AND SCHEMA):\\n\\n\"\n f\"{json.dumps(properties, separators=(',', ':'))}\"\n )\n ]\n if required:\n info_.append(\"\\n\\nRequired fields:\\n\" + \"\\n\".join(required))\n\n info = \"\".join(info_)\n\n input_model_cls = ApifyActorsComponent.create_input_model_class(info)\n tool_cls = ApifyActorsComponent.create_tool_class(self, readme, input_model_cls, actor_id)\n\n return cast(\"Tool\", tool_cls())\n\n @staticmethod\n def create_tool_class(\n parent: \"ApifyActorsComponent\", readme: str, input_model: type[BaseModel], actor_id: str\n ) -> type[BaseTool]:\n \"\"\"Create a tool class that runs an Apify Actor.\"\"\"\n\n class ApifyActorRun(BaseTool):\n \"\"\"Tool that runs Apify Actors.\"\"\"\n\n name: str = f\"apify_actor_{ApifyActorsComponent.actor_id_to_tool_name(actor_id)}\"\n description: str = (\n \"Run an Apify Actor with the given input. \"\n \"Here is a part of the currently loaded Actor README:\\n\\n\"\n f\"{readme}\\n\\n\"\n )\n\n args_schema: type[BaseModel] = input_model\n\n @field_serializer(\"args_schema\")\n def serialize_args_schema(self, args_schema):\n return args_schema.schema()\n\n def _run(self, run_input: str | dict) -> str:\n \"\"\"Use the Apify Actor.\"\"\"\n input_dict = json.loads(run_input) if isinstance(run_input, str) else run_input\n\n # retrieve if nested, just in case\n input_dict = input_dict.get(\"run_input\", input_dict)\n\n res = parent.run_actor(actor_id, input_dict)\n return \"\\n\\n\".join([ApifyActorsComponent.dict_to_json_str(item) for item in res])\n\n return ApifyActorRun\n\n @staticmethod\n def create_input_model_class(description: str) -> type[BaseModel]:\n \"\"\"Create a Pydantic model class for the Actor input.\"\"\"\n\n class ActorInput(BaseModel):\n \"\"\"Input for the Apify Actor tool.\"\"\"\n\n run_input: str = Field(..., description=description)\n\n return ActorInput\n\n def _get_apify_client(self) -> ApifyClient:\n \"\"\"Get the Apify client.\n\n Is created if not exists or token changes.\n \"\"\"\n if not self.apify_token:\n msg = \"API token is required.\"\n raise ValueError(msg)\n # when token changes, create a new client\n if self._apify_client is None or self._apify_client.token != self.apify_token:\n self._apify_client = ApifyClient(self.apify_token)\n if httpx_client := self._apify_client.http_client.httpx_client:\n httpx_client.headers[\"user-agent\"] += \"; Origin/langflow\"\n return self._apify_client\n\n def _get_actor_latest_build(self, actor_id: str) -> dict:\n \"\"\"Get the latest build of an Actor from the default build tag.\"\"\"\n client = self._get_apify_client()\n actor = client.actor(actor_id=actor_id)\n if not (actor_info := actor.get()):\n msg = f\"Actor {actor_id} not found.\"\n raise ValueError(msg)\n\n default_build_tag = actor_info.get(\"defaultRunOptions\", {}).get(\"build\")\n latest_build_id = actor_info.get(\"taggedBuilds\", {}).get(default_build_tag, {}).get(\"buildId\")\n\n if (build := client.build(latest_build_id).get()) is None:\n msg = f\"Build {latest_build_id} not found.\"\n raise ValueError(msg)\n\n return build\n\n @staticmethod\n def get_actor_input_schema_from_build(input_schema: dict) -> tuple[dict, list[str]]:\n \"\"\"Get the input schema from the Actor build.\n\n Trim the description to 250 characters.\n \"\"\"\n properties = input_schema.get(\"properties\", {})\n required = input_schema.get(\"required\", [])\n\n properties_out: dict = {}\n for item, meta in properties.items():\n properties_out[item] = {}\n if desc := meta.get(\"description\"):\n properties_out[item][\"description\"] = (\n desc[:MAX_DESCRIPTION_LEN] + \"...\" if len(desc) > MAX_DESCRIPTION_LEN else desc\n )\n for key_name in (\"type\", \"default\", \"prefill\", \"enum\"):\n if value := meta.get(key_name):\n properties_out[item][key_name] = value\n\n return properties_out, required\n\n def _get_run_dataset_id(self, run_id: str) -> str:\n \"\"\"Get the dataset id from the run id.\"\"\"\n client = self._get_apify_client()\n run = client.run(run_id=run_id)\n if (dataset := run.dataset().get()) is None:\n msg = \"Dataset not found\"\n raise ValueError(msg)\n if (did := dataset.get(\"id\")) is None:\n msg = \"Dataset id not found\"\n raise ValueError(msg)\n return did\n\n @staticmethod\n def dict_to_json_str(d: dict) -> str:\n \"\"\"Convert a dictionary to a JSON string.\"\"\"\n return json.dumps(d, separators=(\",\", \":\"), default=lambda _: \"\")\n\n @staticmethod\n def actor_id_to_tool_name(actor_id: str) -> str:\n \"\"\"Turn actor_id into a valid tool name.\n\n Tool name must only contain letters, numbers, underscores, dashes,\n and cannot contain spaces.\n \"\"\"\n valid_chars = string.ascii_letters + string.digits + \"_-\"\n return \"\".join(char if char in valid_chars else \"_\" for char in actor_id)\n\n def run_actor(self, actor_id: str, run_input: dict, fields: list[str] | None = None) -> list[dict]:\n \"\"\"Run an Apify Actor and return the output dataset.\n\n Args:\n actor_id: Actor name from Apify store to run.\n run_input: JSON input for the Actor.\n fields: List of fields to extract from the dataset. Other fields will be ignored.\n \"\"\"\n client = self._get_apify_client()\n if (details := client.actor(actor_id=actor_id).call(run_input=run_input, wait_secs=1)) is None:\n msg = \"Actor run details not found\"\n raise ValueError(msg)\n if (run_id := details.get(\"id\")) is None:\n msg = \"Run id not found\"\n raise ValueError(msg)\n\n if (run_client := client.run(run_id)) is None:\n msg = \"Run client not found\"\n raise ValueError(msg)\n\n # stream logs\n with run_client.log().stream() as response:\n if response:\n for line in response.iter_lines():\n self.log(line)\n run_client.wait_for_finish()\n\n dataset_id = self._get_run_dataset_id(run_id)\n\n loader = ApifyDatasetLoader(\n dataset_id=dataset_id,\n dataset_mapping_function=lambda item: item\n if not fields\n else {k.replace(\".\", \"_\"): ApifyActorsComponent.get_nested_value(item, k) for k in fields},\n )\n return loader.load()\n\n @staticmethod\n def get_nested_value(data: dict[str, Any], key: str) -> Any:\n \"\"\"Get a nested value from a dictionary.\"\"\"\n keys = key.split(\".\")\n value = data\n for k in keys:\n if not isinstance(value, dict) or k not in value:\n return None\n value = value[k]\n return value\n\n @staticmethod\n def parse_dataset_fields(dataset_fields: str) -> list[str]:\n \"\"\"Convert a string of comma-separated fields into a list of fields.\"\"\"\n dataset_fields = dataset_fields.replace(\"'\", \"\").replace('\"', \"\").replace(\"`\", \"\")\n return [field.strip() for field in dataset_fields.split(\",\")]\n\n @staticmethod\n def flatten(d: dict) -> dict:\n \"\"\"Flatten a nested dictionary.\"\"\"\n\n def items():\n for key, value in d.items():\n if isinstance(value, dict):\n for subkey, subvalue in ApifyActorsComponent.flatten(value).items():\n yield key + \"_\" + subkey, subvalue\n else:\n yield key, value\n\n return dict(items())\n"},"dataset_fields":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Output fields","dynamic":false,"info":"Fields to extract from the dataset, split by commas. Other fields will be ignored. Dots in nested structures will be replaced by underscores. Sample input: 'text, metadata.title'. Sample output: {'text': 'page content here', 'metadata_title': 'page title here'}. For example, for the 'apify/website-content-crawler' Actor, you can extract the 'markdown' field, which is the content of the website in markdown format.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"dataset_fields","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":""},"flatten_dataset":{"_input_type":"BoolInput","advanced":false,"display_name":"Flatten output","dynamic":false,"info":"The output dataset will be converted from a nested format to a flat structure. Dots in nested structure will be replaced by underscores. This is useful for further processing of the Data object. For example, {'a': {'b': 1}} will be flattened to {'a_b': 1}.","list":false,"list_add_label":"Add More","name":"flatten_dataset","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"run_input":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Run input","dynamic":false,"info":"The JSON input for the Actor run. For example for the \"apify/website-content-crawler\" Actor: {\"startUrls\":[{\"url\":\"https://docs.apify.com/academy/web-scraping-for-beginners\"}],\"maxCrawlDepth\":0}","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"run_input","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"{\"startUrls\":[{\"url\":\"https://docs.apify.com/academy/web-scraping-for-beginners\"}],\"maxCrawlDepth\":0}"}},"tool_mode":false}}],["arxiv",{"ArXivComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Search and retrieve papers from arXiv.org","display_name":"arXiv","documentation":"","edited":false,"field_order":["search_query","search_type","max_results"],"frozen":false,"icon":"arXiv","legacy":false,"metadata":{"code_hash":"219239ee2b48","dependencies":{"dependencies":[{"name":"defusedxml","version":"0.7.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.arxiv.arxiv.ArXivComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"search_papers_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 urllib.request\nfrom urllib.parse import urlparse\nfrom xml.etree.ElementTree import Element\n\nfrom defusedxml.ElementTree import fromstring\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, Output\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\n\n\nclass ArXivComponent(Component):\n display_name = \"arXiv\"\n description = \"Search and retrieve papers from arXiv.org\"\n icon = \"arXiv\"\n\n inputs = [\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"The search query for arXiv papers (e.g., 'quantum computing')\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Field\",\n info=\"The field to search in\",\n options=[\"all\", \"title\", \"abstract\", \"author\", \"cat\"], # cat is for category\n value=\"all\",\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"Maximum number of results to return\",\n value=10,\n ),\n ]\n\n outputs = [\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"search_papers_dataframe\"),\n ]\n\n def build_query_url(self) -> str:\n \"\"\"Build the arXiv API query URL.\"\"\"\n base_url = \"http://export.arxiv.org/api/query?\"\n\n # Build the search query based on search type\n if self.search_type == \"all\":\n search_query = self.search_query # No prefix for all fields\n else:\n # Map dropdown values to ArXiv API prefixes\n prefix_map = {\"title\": \"ti\", \"abstract\": \"abs\", \"author\": \"au\", \"cat\": \"cat\"}\n prefix = prefix_map.get(self.search_type, \"\")\n search_query = f\"{prefix}:{self.search_query}\"\n\n # URL parameters\n params = {\n \"search_query\": search_query,\n \"max_results\": str(self.max_results),\n }\n\n # Convert params to URL query string\n query_string = \"&\".join([f\"{k}={urllib.parse.quote(str(v))}\" for k, v in params.items()])\n\n return base_url + query_string\n\n def parse_atom_response(self, response_text: str) -> list[dict]:\n \"\"\"Parse the Atom XML response from arXiv.\"\"\"\n # Parse XML safely using defusedxml\n root = fromstring(response_text)\n\n # Define namespace dictionary for XML parsing\n ns = {\"atom\": \"http://www.w3.org/2005/Atom\", \"arxiv\": \"http://arxiv.org/schemas/atom\"}\n\n papers = []\n # Process each entry (paper)\n for entry in root.findall(\"atom:entry\", ns):\n paper = {\n \"id\": self._get_text(entry, \"atom:id\", ns),\n \"title\": self._get_text(entry, \"atom:title\", ns),\n \"summary\": self._get_text(entry, \"atom:summary\", ns),\n \"published\": self._get_text(entry, \"atom:published\", ns),\n \"updated\": self._get_text(entry, \"atom:updated\", ns),\n \"authors\": [author.find(\"atom:name\", ns).text for author in entry.findall(\"atom:author\", ns)],\n \"arxiv_url\": self._get_link(entry, \"alternate\", ns),\n \"pdf_url\": self._get_link(entry, \"related\", ns),\n \"comment\": self._get_text(entry, \"arxiv:comment\", ns),\n \"journal_ref\": self._get_text(entry, \"arxiv:journal_ref\", ns),\n \"primary_category\": self._get_category(entry, ns),\n \"categories\": [cat.get(\"term\") for cat in entry.findall(\"atom:category\", ns)],\n }\n papers.append(paper)\n\n return papers\n\n def _get_text(self, element: Element, path: str, ns: dict) -> str | None:\n \"\"\"Safely extract text from an XML element.\"\"\"\n el = element.find(path, ns)\n return el.text.strip() if el is not None and el.text else None\n\n def _get_link(self, element: Element, rel: str, ns: dict) -> str | None:\n \"\"\"Get link URL based on relation type.\"\"\"\n for link in element.findall(\"atom:link\", ns):\n if link.get(\"rel\") == rel:\n return link.get(\"href\")\n return None\n\n def _get_category(self, element: Element, ns: dict) -> str | None:\n \"\"\"Get primary category.\"\"\"\n cat = element.find(\"arxiv:primary_category\", ns)\n return cat.get(\"term\") if cat is not None else None\n\n def run_model(self) -> DataFrame:\n return self.search_papers_dataframe()\n\n def search_papers(self) -> list[Data]:\n \"\"\"Search arXiv and return results.\"\"\"\n try:\n # Build the query URL\n url = self.build_query_url()\n\n # Validate URL scheme and host\n parsed_url = urlparse(url)\n if parsed_url.scheme not in {\"http\", \"https\"}:\n error_msg = f\"Invalid URL scheme: {parsed_url.scheme}\"\n raise ValueError(error_msg)\n if parsed_url.hostname != \"export.arxiv.org\":\n error_msg = f\"Invalid host: {parsed_url.hostname}\"\n raise ValueError(error_msg)\n\n # Create a custom opener that only allows http/https schemes\n class RestrictedHTTPHandler(urllib.request.HTTPHandler):\n def http_open(self, req):\n return super().http_open(req)\n\n class RestrictedHTTPSHandler(urllib.request.HTTPSHandler):\n def https_open(self, req):\n return super().https_open(req)\n\n # Build opener with restricted handlers\n opener = urllib.request.build_opener(RestrictedHTTPHandler, RestrictedHTTPSHandler)\n urllib.request.install_opener(opener)\n\n # Make the request with validated URL using restricted opener\n response = opener.open(url)\n response_text = response.read().decode(\"utf-8\")\n\n # Parse the response\n papers = self.parse_atom_response(response_text)\n\n # Convert to Data objects\n results = [Data(data=paper) for paper in papers]\n self.status = results\n except (urllib.error.URLError, ValueError) as e:\n error_data = Data(data={\"error\": f\"Request error: {e!s}\"})\n self.status = error_data\n return [error_data]\n else:\n return results\n\n def search_papers_dataframe(self) -> DataFrame:\n \"\"\"Convert the Arxiv search results to a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the search results.\n \"\"\"\n data = self.search_papers()\n return DataFrame(data)\n"},"max_results":{"_input_type":"IntInput","advanced":false,"display_name":"Max Results","dynamic":false,"info":"Maximum number of results to return","list":false,"list_add_label":"Add More","name":"max_results","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":10},"search_query":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"The search query for arXiv papers (e.g., 'quantum computing')","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"search_type":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Search Field","dynamic":false,"external_options":{},"info":"The field to search in","name":"search_type","options":["all","title","abstract","author","cat"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"all"}},"tool_mode":false}}],["assemblyai",{"AssemblyAIGetSubtitles":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Export your transcript in SRT or VTT format for subtitles and closed captions","display_name":"AssemblyAI Get Subtitles","documentation":"https://www.assemblyai.com/docs","edited":false,"field_order":["api_key","transcription_result","subtitle_format","chars_per_caption"],"frozen":false,"icon":"AssemblyAI","legacy":false,"metadata":{"code_hash":"533d1fcf7c7a","dependencies":{"dependencies":[{"name":"assemblyai","version":"0.35.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.assemblyai.assemblyai_get_subtitles.AssemblyAIGetSubtitles"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Subtitles","group_outputs":false,"method":"get_subtitles","name":"subtitles","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Assembly API Key","dynamic":false,"info":"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/","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":""},"chars_per_caption":{"_input_type":"IntInput","advanced":true,"display_name":"Characters per Caption","dynamic":false,"info":"The maximum number of characters per caption (0 for no limit)","list":false,"list_add_label":"Add More","name":"chars_per_caption","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":0},"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 assemblyai as aai\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import DataInput, DropdownInput, IntInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass AssemblyAIGetSubtitles(Component):\n display_name = \"AssemblyAI Get Subtitles\"\n description = \"Export your transcript in SRT or VTT format for subtitles and closed captions\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n DataInput(\n name=\"transcription_result\",\n display_name=\"Transcription Result\",\n info=\"The transcription result from AssemblyAI\",\n required=True,\n ),\n DropdownInput(\n name=\"subtitle_format\",\n display_name=\"Subtitle Format\",\n options=[\"srt\", \"vtt\"],\n value=\"srt\",\n info=\"The format of the captions (SRT or VTT)\",\n ),\n IntInput(\n name=\"chars_per_caption\",\n display_name=\"Characters per Caption\",\n info=\"The maximum number of characters per caption (0 for no limit)\",\n value=0,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Subtitles\", name=\"subtitles\", method=\"get_subtitles\"),\n ]\n\n def get_subtitles(self) -> Data:\n aai.settings.api_key = self.api_key\n\n # check if it's an error message from the previous step\n if self.transcription_result.data.get(\"error\"):\n self.status = self.transcription_result.data[\"error\"]\n return self.transcription_result\n\n try:\n transcript_id = self.transcription_result.data[\"id\"]\n transcript = aai.Transcript.get_by_id(transcript_id)\n except Exception as e: # noqa: BLE001\n error = f\"Getting transcription failed: {e}\"\n logger.debug(error, exc_info=True)\n self.status = error\n return Data(data={\"error\": error})\n\n if transcript.status == aai.TranscriptStatus.completed:\n subtitles = None\n chars_per_caption = self.chars_per_caption if self.chars_per_caption > 0 else None\n if self.subtitle_format == \"srt\":\n subtitles = transcript.export_subtitles_srt(chars_per_caption)\n else:\n subtitles = transcript.export_subtitles_vtt(chars_per_caption)\n\n result = Data(\n subtitles=subtitles,\n format=self.subtitle_format,\n transcript_id=transcript_id,\n chars_per_caption=chars_per_caption,\n )\n\n self.status = result\n return result\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n"},"subtitle_format":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Subtitle Format","dynamic":false,"external_options":{},"info":"The format of the captions (SRT or VTT)","name":"subtitle_format","options":["srt","vtt"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"srt"},"transcription_result":{"_input_type":"DataInput","advanced":false,"display_name":"Transcription Result","dynamic":false,"info":"The transcription result from AssemblyAI","input_types":["Data"],"list":false,"list_add_label":"Add More","name":"transcription_result","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""}},"tool_mode":false},"AssemblyAILeMUR":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Apply Large Language Models to spoken data using the AssemblyAI LeMUR framework","display_name":"AssemblyAI LeMUR","documentation":"https://www.assemblyai.com/docs/lemur","edited":false,"field_order":["api_key","transcription_result","prompt","final_model","temperature","max_output_size","endpoint","questions","transcript_ids"],"frozen":false,"icon":"AssemblyAI","legacy":false,"metadata":{"code_hash":"8c96738ab967","dependencies":{"dependencies":[{"name":"assemblyai","version":"0.35.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.assemblyai.assemblyai_lemur.AssemblyAILeMUR"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"LeMUR Response","group_outputs":false,"method":"run_lemur","name":"lemur_response","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Assembly API Key","dynamic":false,"info":"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/","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 assemblyai as aai\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import DataInput, DropdownInput, FloatInput, IntInput, MultilineInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass AssemblyAILeMUR(Component):\n display_name = \"AssemblyAI LeMUR\"\n description = \"Apply Large Language Models to spoken data using the AssemblyAI LeMUR framework\"\n documentation = \"https://www.assemblyai.com/docs/lemur\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n advanced=False,\n required=True,\n ),\n DataInput(\n name=\"transcription_result\",\n display_name=\"Transcription Result\",\n info=\"The transcription result from AssemblyAI\",\n required=True,\n ),\n MultilineInput(name=\"prompt\", display_name=\"Input Prompt\", info=\"The text to prompt the model\", required=True),\n DropdownInput(\n name=\"final_model\",\n display_name=\"Final Model\",\n options=[\"claude3_5_sonnet\", \"claude3_opus\", \"claude3_haiku\", \"claude3_sonnet\"],\n value=\"claude3_5_sonnet\",\n info=\"The model that is used for the final prompt after compression is performed\",\n advanced=True,\n ),\n FloatInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n advanced=True,\n value=0.0,\n info=\"The temperature to use for the model\",\n ),\n IntInput(\n name=\"max_output_size\",\n display_name=\" Max Output Size\",\n advanced=True,\n value=2000,\n info=\"Max output size in tokens, up to 4000\",\n ),\n DropdownInput(\n name=\"endpoint\",\n display_name=\"Endpoint\",\n options=[\"task\", \"summary\", \"question-answer\"],\n value=\"task\",\n info=(\n \"The LeMUR endpoint to use. For 'summary' and 'question-answer',\"\n \" no prompt input is needed. See https://www.assemblyai.com/docs/api-reference/lemur/ for more info.\"\n ),\n advanced=True,\n ),\n MultilineInput(\n name=\"questions\",\n display_name=\"Questions\",\n info=\"Comma-separated list of your questions. Only used if Endpoint is 'question-answer'\",\n advanced=True,\n ),\n MultilineInput(\n name=\"transcript_ids\",\n display_name=\"Transcript IDs\",\n info=(\n \"Comma-separated list of transcript IDs. LeMUR can perform actions over multiple transcripts.\"\n \" If provided, the Transcription Result is ignored.\"\n ),\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"LeMUR Response\", name=\"lemur_response\", method=\"run_lemur\"),\n ]\n\n def run_lemur(self) -> Data:\n \"\"\"Use the LeMUR task endpoint to input the LLM prompt.\"\"\"\n aai.settings.api_key = self.api_key\n\n if not self.transcription_result and not self.transcript_ids:\n error = \"Either a Transcription Result or Transcript IDs must be provided\"\n self.status = error\n return Data(data={\"error\": error})\n if self.transcription_result and self.transcription_result.data.get(\"error\"):\n # error message from the previous step\n self.status = self.transcription_result.data[\"error\"]\n return self.transcription_result\n if self.endpoint == \"task\" and not self.prompt:\n self.status = \"No prompt specified for the task endpoint\"\n return Data(data={\"error\": \"No prompt specified\"})\n if self.endpoint == \"question-answer\" and not self.questions:\n error = \"No Questions were provided for the question-answer endpoint\"\n self.status = error\n return Data(data={\"error\": error})\n\n # Check for valid transcripts\n transcript_ids = None\n if self.transcription_result and \"id\" in self.transcription_result.data:\n transcript_ids = [self.transcription_result.data[\"id\"]]\n elif self.transcript_ids:\n transcript_ids = self.transcript_ids.split(\",\") or []\n transcript_ids = [t.strip() for t in transcript_ids]\n\n if not transcript_ids:\n error = \"Either a valid Transcription Result or valid Transcript IDs must be provided\"\n self.status = error\n return Data(data={\"error\": error})\n\n # Get TranscriptGroup and check if there is any error\n transcript_group = aai.TranscriptGroup(transcript_ids=transcript_ids)\n transcript_group, failures = transcript_group.wait_for_completion(return_failures=True)\n if failures:\n error = f\"Getting transcriptions failed: {failures[0]}\"\n self.status = error\n return Data(data={\"error\": error})\n\n for t in transcript_group.transcripts:\n if t.status == aai.TranscriptStatus.error:\n self.status = t.error\n return Data(data={\"error\": t.error})\n\n # Perform LeMUR action\n try:\n response = self.perform_lemur_action(transcript_group, self.endpoint)\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error running LeMUR\", exc_info=True)\n error = f\"An Error happened: {e}\"\n self.status = error\n return Data(data={\"error\": error})\n\n result = Data(data=response)\n self.status = result\n return result\n\n def perform_lemur_action(self, transcript_group: aai.TranscriptGroup, endpoint: str) -> dict:\n logger.info(\"Endpoint:\", endpoint, type(endpoint))\n if endpoint == \"task\":\n result = transcript_group.lemur.task(\n prompt=self.prompt,\n final_model=self.get_final_model(self.final_model),\n temperature=self.temperature,\n max_output_size=self.max_output_size,\n )\n elif endpoint == \"summary\":\n result = transcript_group.lemur.summarize(\n final_model=self.get_final_model(self.final_model),\n temperature=self.temperature,\n max_output_size=self.max_output_size,\n )\n elif endpoint == \"question-answer\":\n questions = self.questions.split(\",\")\n questions = [aai.LemurQuestion(question=q) for q in questions]\n result = transcript_group.lemur.question(\n questions=questions,\n final_model=self.get_final_model(self.final_model),\n temperature=self.temperature,\n max_output_size=self.max_output_size,\n )\n else:\n msg = f\"Endpoint not supported: {endpoint}\"\n raise ValueError(msg)\n\n return result.dict()\n\n def get_final_model(self, model_name: str) -> aai.LemurModel:\n if model_name == \"claude3_5_sonnet\":\n return aai.LemurModel.claude3_5_sonnet\n if model_name == \"claude3_opus\":\n return aai.LemurModel.claude3_opus\n if model_name == \"claude3_haiku\":\n return aai.LemurModel.claude3_haiku\n if model_name == \"claude3_sonnet\":\n return aai.LemurModel.claude3_sonnet\n msg = f\"Model name not supported: {model_name}\"\n raise ValueError(msg)\n"},"endpoint":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Endpoint","dynamic":false,"external_options":{},"info":"The LeMUR endpoint to use. For 'summary' and 'question-answer', no prompt input is needed. See https://www.assemblyai.com/docs/api-reference/lemur/ for more info.","name":"endpoint","options":["task","summary","question-answer"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"task"},"final_model":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Final Model","dynamic":false,"external_options":{},"info":"The model that is used for the final prompt after compression is performed","name":"final_model","options":["claude3_5_sonnet","claude3_opus","claude3_haiku","claude3_sonnet"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"claude3_5_sonnet"},"max_output_size":{"_input_type":"IntInput","advanced":true,"display_name":" Max Output Size","dynamic":false,"info":"Max output size in tokens, up to 4000","list":false,"list_add_label":"Add More","name":"max_output_size","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":2000},"prompt":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Input Prompt","dynamic":false,"info":"The text to prompt the model","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"prompt","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"questions":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Questions","dynamic":false,"info":"Comma-separated list of your questions. Only used if Endpoint is 'question-answer'","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"questions","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":""},"temperature":{"_input_type":"FloatInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"The temperature to use for the model","list":false,"list_add_label":"Add More","name":"temperature","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.0},"transcript_ids":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Transcript IDs","dynamic":false,"info":"Comma-separated list of transcript IDs. LeMUR can perform actions over multiple transcripts. If provided, the Transcription Result is ignored.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"transcript_ids","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":""},"transcription_result":{"_input_type":"DataInput","advanced":false,"display_name":"Transcription Result","dynamic":false,"info":"The transcription result from AssemblyAI","input_types":["Data"],"list":false,"list_add_label":"Add More","name":"transcription_result","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""}},"tool_mode":false},"AssemblyAIListTranscripts":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Retrieve a list of transcripts from AssemblyAI with filtering options","display_name":"AssemblyAI List Transcripts","documentation":"https://www.assemblyai.com/docs","edited":false,"field_order":["api_key","limit","status_filter","created_on","throttled_only"],"frozen":false,"icon":"AssemblyAI","legacy":false,"metadata":{"code_hash":"267dcda48ad4","dependencies":{"dependencies":[{"name":"assemblyai","version":"0.35.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.assemblyai.assemblyai_list_transcripts.AssemblyAIListTranscripts"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Transcript List","group_outputs":false,"method":"list_transcripts","name":"transcript_list","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Assembly API Key","dynamic":false,"info":"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/","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 assemblyai as aai\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass AssemblyAIListTranscripts(Component):\n display_name = \"AssemblyAI List Transcripts\"\n description = \"Retrieve a list of transcripts from AssemblyAI with filtering options\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n IntInput(\n name=\"limit\",\n display_name=\"Limit\",\n info=\"Maximum number of transcripts to retrieve (default: 20, use 0 for all)\",\n value=20,\n ),\n DropdownInput(\n name=\"status_filter\",\n display_name=\"Status Filter\",\n options=[\"all\", \"queued\", \"processing\", \"completed\", \"error\"],\n value=\"all\",\n info=\"Filter by transcript status\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"created_on\",\n display_name=\"Created On\",\n info=\"Only get transcripts created on this date (YYYY-MM-DD)\",\n advanced=True,\n ),\n BoolInput(\n name=\"throttled_only\",\n display_name=\"Throttled Only\",\n info=\"Only get throttled transcripts, overrides the status filter\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcript List\", name=\"transcript_list\", method=\"list_transcripts\"),\n ]\n\n def list_transcripts(self) -> list[Data]:\n aai.settings.api_key = self.api_key\n\n params = aai.ListTranscriptParameters()\n if self.limit:\n params.limit = self.limit\n if self.status_filter != \"all\":\n params.status = self.status_filter\n if self.created_on and self.created_on.text:\n params.created_on = self.created_on.text\n if self.throttled_only:\n params.throttled_only = True\n\n try:\n transcriber = aai.Transcriber()\n\n def convert_page_to_data_list(page):\n return [Data(**t.dict()) for t in page.transcripts]\n\n if self.limit == 0:\n # paginate over all pages\n params.limit = 100\n page = transcriber.list_transcripts(params)\n transcripts = convert_page_to_data_list(page)\n\n while page.page_details.before_id_of_prev_url is not None:\n params.before_id = page.page_details.before_id_of_prev_url\n page = transcriber.list_transcripts(params)\n transcripts.extend(convert_page_to_data_list(page))\n else:\n # just one page\n page = transcriber.list_transcripts(params)\n transcripts = convert_page_to_data_list(page)\n\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error listing transcripts\", exc_info=True)\n error_data = Data(data={\"error\": f\"An error occurred: {e}\"})\n self.status = [error_data]\n return [error_data]\n\n self.status = transcripts\n return transcripts\n"},"created_on":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Created On","dynamic":false,"info":"Only get transcripts created on this date (YYYY-MM-DD)","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"created_on","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":""},"limit":{"_input_type":"IntInput","advanced":false,"display_name":"Limit","dynamic":false,"info":"Maximum number of transcripts to retrieve (default: 20, use 0 for all)","list":false,"list_add_label":"Add More","name":"limit","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":20},"status_filter":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Status Filter","dynamic":false,"external_options":{},"info":"Filter by transcript status","name":"status_filter","options":["all","queued","processing","completed","error"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"all"},"throttled_only":{"_input_type":"BoolInput","advanced":true,"display_name":"Throttled Only","dynamic":false,"info":"Only get throttled transcripts, overrides the status filter","list":false,"list_add_label":"Add More","name":"throttled_only","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"AssemblyAITranscriptionJobCreator":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Create a transcription job for an audio file using AssemblyAI with advanced options","display_name":"AssemblyAI Start Transcript","documentation":"https://www.assemblyai.com/docs","edited":false,"field_order":["api_key","audio_file","audio_file_url","speech_model","language_detection","language_code","speaker_labels","speakers_expected","punctuate","format_text"],"frozen":false,"icon":"AssemblyAI","legacy":false,"metadata":{"code_hash":"7ff7b3f90298","dependencies":{"dependencies":[{"name":"assemblyai","version":"0.35.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.assemblyai.assemblyai_start_transcript.AssemblyAITranscriptionJobCreator"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Transcript ID","group_outputs":false,"method":"create_transcription_job","name":"transcript_id","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Assembly API Key","dynamic":false,"info":"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/","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":""},"audio_file":{"_input_type":"FileInput","advanced":false,"display_name":"Audio File","dynamic":false,"fileTypes":["3ga","8svx","aac","ac3","aif","aiff","alac","amr","ape","au","dss","flac","flv","m4a","m4b","m4p","m4r","mp3","mpga","ogg","oga","mogg","opus","qcp","tta","voc","wav","wma","wv","webm","mts","m2ts","ts","mov","mp2","mp4","m4p","m4v","mxf"],"file_path":"","info":"The audio file to transcribe","list":false,"list_add_label":"Add More","name":"audio_file","override_skip":false,"placeholder":"","required":true,"show":true,"temp_file":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"file","value":""},"audio_file_url":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Audio File URL","dynamic":false,"info":"The URL of the audio file to transcribe (Can be used instead of a File)","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"audio_file_url","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":""},"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":"from pathlib import Path\n\nimport assemblyai as aai\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DropdownInput, FileInput, MessageTextInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass AssemblyAITranscriptionJobCreator(Component):\n display_name = \"AssemblyAI Start Transcript\"\n description = \"Create a transcription job for an audio file using AssemblyAI with advanced options\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n FileInput(\n name=\"audio_file\",\n display_name=\"Audio File\",\n file_types=[\n \"3ga\",\n \"8svx\",\n \"aac\",\n \"ac3\",\n \"aif\",\n \"aiff\",\n \"alac\",\n \"amr\",\n \"ape\",\n \"au\",\n \"dss\",\n \"flac\",\n \"flv\",\n \"m4a\",\n \"m4b\",\n \"m4p\",\n \"m4r\",\n \"mp3\",\n \"mpga\",\n \"ogg\",\n \"oga\",\n \"mogg\",\n \"opus\",\n \"qcp\",\n \"tta\",\n \"voc\",\n \"wav\",\n \"wma\",\n \"wv\",\n \"webm\",\n \"mts\",\n \"m2ts\",\n \"ts\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"m4p\",\n \"m4v\",\n \"mxf\",\n ],\n info=\"The audio file to transcribe\",\n required=True,\n ),\n MessageTextInput(\n name=\"audio_file_url\",\n display_name=\"Audio File URL\",\n info=\"The URL of the audio file to transcribe (Can be used instead of a File)\",\n advanced=True,\n ),\n DropdownInput(\n name=\"speech_model\",\n display_name=\"Speech Model\",\n options=[\n \"best\",\n \"nano\",\n ],\n value=\"best\",\n info=\"The speech model to use for the transcription\",\n advanced=True,\n ),\n BoolInput(\n name=\"language_detection\",\n display_name=\"Automatic Language Detection\",\n info=\"Enable automatic language detection\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"language_code\",\n display_name=\"Language\",\n info=(\n \"\"\"\n The language of the audio file. Can be set manually if automatic language detection is disabled.\n See https://www.assemblyai.com/docs/getting-started/supported-languages \"\"\"\n \"for a list of supported language codes.\"\n ),\n advanced=True,\n ),\n BoolInput(\n name=\"speaker_labels\",\n display_name=\"Enable Speaker Labels\",\n info=\"Enable speaker diarization\",\n ),\n MessageTextInput(\n name=\"speakers_expected\",\n display_name=\"Expected Number of Speakers\",\n info=\"Set the expected number of speakers (optional, enter a number)\",\n advanced=True,\n ),\n BoolInput(\n name=\"punctuate\",\n display_name=\"Punctuate\",\n info=\"Enable automatic punctuation\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"format_text\",\n display_name=\"Format Text\",\n info=\"Enable text formatting\",\n advanced=True,\n value=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcript ID\", name=\"transcript_id\", method=\"create_transcription_job\"),\n ]\n\n def create_transcription_job(self) -> Data:\n aai.settings.api_key = self.api_key\n\n # Convert speakers_expected to int if it's not empty\n speakers_expected = None\n if self.speakers_expected and self.speakers_expected.strip():\n try:\n speakers_expected = int(self.speakers_expected)\n except ValueError:\n self.status = \"Error: Expected Number of Speakers must be a valid integer\"\n return Data(data={\"error\": \"Error: Expected Number of Speakers must be a valid integer\"})\n\n language_code = self.language_code or None\n\n config = aai.TranscriptionConfig(\n speech_model=self.speech_model,\n language_detection=self.language_detection,\n language_code=language_code,\n speaker_labels=self.speaker_labels,\n speakers_expected=speakers_expected,\n punctuate=self.punctuate,\n format_text=self.format_text,\n )\n\n audio = None\n if self.audio_file:\n if self.audio_file_url:\n logger.warning(\"Both an audio file an audio URL were specified. The audio URL was ignored.\")\n\n # Check if the file exists\n if not Path(self.audio_file).exists():\n self.status = \"Error: Audio file not found\"\n return Data(data={\"error\": \"Error: Audio file not found\"})\n audio = self.audio_file\n elif self.audio_file_url:\n audio = self.audio_file_url\n else:\n self.status = \"Error: Either an audio file or an audio URL must be specified\"\n return Data(data={\"error\": \"Error: Either an audio file or an audio URL must be specified\"})\n\n try:\n transcript = aai.Transcriber().submit(audio, config=config)\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error submitting transcription job\", exc_info=True)\n self.status = f\"An error occurred: {e}\"\n return Data(data={\"error\": f\"An error occurred: {e}\"})\n\n if transcript.error:\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n result = Data(data={\"transcript_id\": transcript.id})\n self.status = result\n return result\n"},"format_text":{"_input_type":"BoolInput","advanced":true,"display_name":"Format Text","dynamic":false,"info":"Enable text formatting","list":false,"list_add_label":"Add More","name":"format_text","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"language_code":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Language","dynamic":false,"info":"\n The language of the audio file. Can be set manually if automatic language detection is disabled.\n See https://www.assemblyai.com/docs/getting-started/supported-languages for a list of supported language codes.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"language_code","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":""},"language_detection":{"_input_type":"BoolInput","advanced":true,"display_name":"Automatic Language Detection","dynamic":false,"info":"Enable automatic language detection","list":false,"list_add_label":"Add More","name":"language_detection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"punctuate":{"_input_type":"BoolInput","advanced":true,"display_name":"Punctuate","dynamic":false,"info":"Enable automatic punctuation","list":false,"list_add_label":"Add More","name":"punctuate","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"speaker_labels":{"_input_type":"BoolInput","advanced":false,"display_name":"Enable Speaker Labels","dynamic":false,"info":"Enable speaker diarization","list":false,"list_add_label":"Add More","name":"speaker_labels","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"speakers_expected":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Expected Number of Speakers","dynamic":false,"info":"Set the expected number of speakers (optional, enter a number)","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"speakers_expected","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":""},"speech_model":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Speech Model","dynamic":false,"external_options":{},"info":"The speech model to use for the transcription","name":"speech_model","options":["best","nano"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"best"}},"tool_mode":false},"AssemblyAITranscriptionJobPoller":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Poll for the status of a transcription job using AssemblyAI","display_name":"AssemblyAI Poll Transcript","documentation":"https://www.assemblyai.com/docs","edited":false,"field_order":["api_key","transcript_id","polling_interval"],"frozen":false,"icon":"AssemblyAI","legacy":false,"metadata":{"code_hash":"935c9296b149","dependencies":{"dependencies":[{"name":"assemblyai","version":"0.35.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.assemblyai.assemblyai_poll_transcript.AssemblyAITranscriptionJobPoller"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Transcription Result","group_outputs":false,"method":"poll_transcription_job","name":"transcription_result","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Assembly API Key","dynamic":false,"info":"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/","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 assemblyai as aai\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import DataInput, FloatInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass AssemblyAITranscriptionJobPoller(Component):\n display_name = \"AssemblyAI Poll Transcript\"\n description = \"Poll for the status of a transcription job using AssemblyAI\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n DataInput(\n name=\"transcript_id\",\n display_name=\"Transcript ID\",\n info=\"The ID of the transcription job to poll\",\n required=True,\n ),\n FloatInput(\n name=\"polling_interval\",\n display_name=\"Polling Interval\",\n value=3.0,\n info=\"The polling interval in seconds\",\n advanced=True,\n range_spec=RangeSpec(min=3, max=30),\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcription Result\", name=\"transcription_result\", method=\"poll_transcription_job\"),\n ]\n\n def poll_transcription_job(self) -> Data:\n \"\"\"Polls the transcription status until completion and returns the Data.\"\"\"\n aai.settings.api_key = self.api_key\n aai.settings.polling_interval = self.polling_interval\n\n # check if it's an error message from the previous step\n if self.transcript_id.data.get(\"error\"):\n self.status = self.transcript_id.data[\"error\"]\n return self.transcript_id\n\n try:\n transcript = aai.Transcript.get_by_id(self.transcript_id.data[\"transcript_id\"])\n except Exception as e: # noqa: BLE001\n error = f\"Getting transcription failed: {e}\"\n logger.debug(error, exc_info=True)\n self.status = error\n return Data(data={\"error\": error})\n\n if transcript.status == aai.TranscriptStatus.completed:\n json_response = transcript.json_response\n text = json_response.pop(\"text\", None)\n utterances = json_response.pop(\"utterances\", None)\n transcript_id = json_response.pop(\"id\", None)\n sorted_data = {\"text\": text, \"utterances\": utterances, \"id\": transcript_id}\n sorted_data.update(json_response)\n data = Data(data=sorted_data)\n self.status = data\n return data\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n"},"polling_interval":{"_input_type":"FloatInput","advanced":true,"display_name":"Polling Interval","dynamic":false,"info":"The polling interval in seconds","list":false,"list_add_label":"Add More","name":"polling_interval","override_skip":false,"placeholder":"","range_spec":{"max":30.0,"min":3.0,"step":0.1,"step_type":"float"},"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":3.0},"transcript_id":{"_input_type":"DataInput","advanced":false,"display_name":"Transcript ID","dynamic":false,"info":"The ID of the transcription job to poll","input_types":["Data"],"list":false,"list_add_label":"Add More","name":"transcript_id","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""}},"tool_mode":false}}],["azure",{"AzureOpenAIEmbeddings":{"base_classes":["Embeddings"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate embeddings using Azure OpenAI models.","display_name":"Azure OpenAI Embeddings","documentation":"https://python.langchain.com/docs/integrations/text_embedding/azureopenai","edited":false,"field_order":["model","azure_endpoint","azure_deployment","api_version","api_key","dimensions"],"frozen":false,"icon":"Azure","legacy":false,"metadata":{"code_hash":"6b54f3243a6a","dependencies":{"dependencies":[{"name":"langchain_openai","version":"0.3.23"},{"name":"lfx","version":null}],"total_dependencies":2},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.azure.azure_openai_embeddings.AzureOpenAIEmbeddingsComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Embeddings","group_outputs":false,"method":"build_embeddings","name":"embeddings","selected":"Embeddings","tool_mode":true,"types":["Embeddings"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Azure OpenAI API Key","dynamic":false,"info":"","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":""},"api_version":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"API Version","dynamic":false,"external_options":{},"info":"","name":"api_version","options":["2022-12-01","2023-03-15-preview","2023-05-15","2023-06-01-preview","2023-07-01-preview","2023-08-01-preview"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"2023-08-01-preview"},"azure_deployment":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Deployment Name","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"azure_deployment","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"azure_endpoint":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Azure Endpoint","dynamic":false,"info":"Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"azure_endpoint","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"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":"from langchain_openai import AzureOpenAIEmbeddings\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\n\n\nclass AzureOpenAIEmbeddingsComponent(LCModelComponent):\n display_name: str = \"Azure OpenAI Embeddings\"\n description: str = \"Generate embeddings using Azure OpenAI models.\"\n documentation: str = \"https://python.langchain.com/docs/integrations/text_embedding/azureopenai\"\n icon = \"Azure\"\n name = \"AzureOpenAIEmbeddings\"\n\n API_VERSION_OPTIONS = [\n \"2022-12-01\",\n \"2023-03-15-preview\",\n \"2023-05-15\",\n \"2023-06-01-preview\",\n \"2023-07-01-preview\",\n \"2023-08-01-preview\",\n ]\n\n inputs = [\n DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n advanced=False,\n options=OPENAI_EMBEDDING_MODEL_NAMES,\n value=OPENAI_EMBEDDING_MODEL_NAMES[0],\n ),\n MessageTextInput(\n name=\"azure_endpoint\",\n display_name=\"Azure Endpoint\",\n required=True,\n info=\"Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`\",\n ),\n MessageTextInput(\n name=\"azure_deployment\",\n display_name=\"Deployment Name\",\n required=True,\n ),\n DropdownInput(\n name=\"api_version\",\n display_name=\"API Version\",\n options=API_VERSION_OPTIONS,\n value=API_VERSION_OPTIONS[-1],\n advanced=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Azure OpenAI API Key\",\n required=True,\n ),\n IntInput(\n name=\"dimensions\",\n display_name=\"Dimensions\",\n info=\"The number of dimensions the resulting output embeddings should have. \"\n \"Only supported by certain models.\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n try:\n embeddings = AzureOpenAIEmbeddings(\n model=self.model,\n azure_endpoint=self.azure_endpoint,\n azure_deployment=self.azure_deployment,\n api_version=self.api_version,\n api_key=self.api_key,\n dimensions=self.dimensions or None,\n )\n except Exception as e:\n msg = f\"Could not connect to AzureOpenAIEmbeddings API: {e}\"\n raise ValueError(msg) from e\n\n return embeddings\n"},"dimensions":{"_input_type":"IntInput","advanced":true,"display_name":"Dimensions","dynamic":false,"info":"The number of dimensions the resulting output embeddings should have. Only supported by certain models.","list":false,"list_add_label":"Add More","name":"dimensions","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":""},"model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model","dynamic":false,"external_options":{},"info":"","name":"model","options":["text-embedding-3-small","text-embedding-3-large","text-embedding-ada-002"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"text-embedding-3-small"}},"tool_mode":false},"AzureOpenAIModel":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate text using Azure OpenAI LLMs.","display_name":"Azure OpenAI","documentation":"https://python.langchain.com/docs/integrations/llms/azure_openai","edited":false,"field_order":["input_value","system_message","stream","azure_endpoint","azure_deployment","api_key","api_version","temperature","max_tokens"],"frozen":false,"icon":"Azure","legacy":false,"metadata":{"code_hash":"cc8d003556d8","dependencies":{"dependencies":[{"name":"langchain_openai","version":"0.3.23"},{"name":"lfx","version":null}],"total_dependencies":2},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.azure.azure_openai.AzureChatOpenAIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Azure Chat OpenAI API Key","dynamic":false,"info":"","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":""},"api_version":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"API Version","dynamic":false,"external_options":{},"info":"","name":"api_version","options":["2025-02-01-preview","2025-01-01-preview","2024-12-01-preview","2024-10-01-preview","2024-09-01-preview","2024-08-01-preview","2024-07-01-preview","2024-06-01","2024-03-01-preview","2024-02-15-preview","2023-12-01-preview","2023-05-15"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"2024-06-01"},"azure_deployment":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Deployment Name","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"azure_deployment","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"azure_endpoint":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Azure Endpoint","dynamic":false,"info":"Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"azure_endpoint","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"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":"from langchain_openai import AzureChatOpenAI\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import MessageTextInput\nfrom lfx.io import DropdownInput, IntInput, SecretStrInput, SliderInput\n\n\nclass AzureChatOpenAIComponent(LCModelComponent):\n display_name: str = \"Azure OpenAI\"\n description: str = \"Generate text using Azure OpenAI LLMs.\"\n documentation: str = \"https://python.langchain.com/docs/integrations/llms/azure_openai\"\n beta = False\n icon = \"Azure\"\n name = \"AzureOpenAIModel\"\n\n AZURE_OPENAI_API_VERSIONS = [\n \"2024-06-01\",\n \"2024-07-01-preview\",\n \"2024-08-01-preview\",\n \"2024-09-01-preview\",\n \"2024-10-01-preview\",\n \"2023-05-15\",\n \"2023-12-01-preview\",\n \"2024-02-15-preview\",\n \"2024-03-01-preview\",\n \"2024-12-01-preview\",\n \"2025-01-01-preview\",\n \"2025-02-01-preview\",\n ]\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n MessageTextInput(\n name=\"azure_endpoint\",\n display_name=\"Azure Endpoint\",\n info=\"Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`\",\n required=True,\n ),\n MessageTextInput(name=\"azure_deployment\", display_name=\"Deployment Name\", required=True),\n SecretStrInput(name=\"api_key\", display_name=\"Azure Chat OpenAI API Key\", required=True),\n DropdownInput(\n name=\"api_version\",\n display_name=\"API Version\",\n options=sorted(AZURE_OPENAI_API_VERSIONS, reverse=True),\n value=next(\n (\n version\n for version in sorted(AZURE_OPENAI_API_VERSIONS, reverse=True)\n if not version.endswith(\"-preview\")\n ),\n AZURE_OPENAI_API_VERSIONS[0],\n ),\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.7,\n range_spec=RangeSpec(min=0, max=2, step=0.01),\n info=\"Controls randomness. Lower values are more deterministic, higher values are more creative.\",\n advanced=True,\n ),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n azure_endpoint = self.azure_endpoint\n azure_deployment = self.azure_deployment\n api_version = self.api_version\n api_key = self.api_key\n temperature = self.temperature\n max_tokens = self.max_tokens\n stream = self.stream\n\n try:\n output = AzureChatOpenAI(\n azure_endpoint=azure_endpoint,\n azure_deployment=azure_deployment,\n api_version=api_version,\n api_key=api_key,\n temperature=temperature,\n max_tokens=max_tokens or None,\n streaming=stream,\n )\n except Exception as e:\n msg = f\"Could not connect to AzureOpenAI API: {e}\"\n raise ValueError(msg) from e\n\n return output\n"},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Max Tokens","dynamic":false,"info":"The maximum number of tokens to generate. Set to 0 for unlimited tokens.","list":false,"list_add_label":"Add More","name":"max_tokens","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":""},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"SliderInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"Controls randomness. Lower values are more deterministic, higher values are more creative.","max_label":"","max_label_icon":"","min_label":"","min_label_icon":"","name":"temperature","override_skip":false,"placeholder":"","range_spec":{"max":2.0,"min":0.0,"step":0.01,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":0.7}},"tool_mode":false}}],["baidu",{"BaiduQianfanChatModel":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate text using Baidu Qianfan LLMs.","display_name":"Qianfan","documentation":"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint","edited":false,"field_order":["input_value","system_message","stream","model","qianfan_ak","qianfan_sk","top_p","temperature","penalty_score","endpoint"],"frozen":false,"icon":"BaiduQianfan","legacy":false,"metadata":{"code_hash":"a5fdfdb5757f","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null}],"total_dependencies":2},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.baidu.baidu_qianfan_chat.QianfanChatEndpointComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing.constants import LanguageModel\nfrom lfx.io import DropdownInput, FloatInput, MessageTextInput, SecretStrInput\n\n\nclass QianfanChatEndpointComponent(LCModelComponent):\n display_name: str = \"Qianfan\"\n description: str = \"Generate text using Baidu Qianfan LLMs.\"\n documentation: str = \"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint\"\n icon = \"BaiduQianfan\"\n name = \"BaiduQianfanChatModel\"\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n DropdownInput(\n name=\"model\",\n display_name=\"Model Name\",\n options=[\n \"EB-turbo-AppBuilder\",\n \"Llama-2-70b-chat\",\n \"ERNIE-Bot-turbo-AI\",\n \"ERNIE-Lite-8K-0308\",\n \"ERNIE-Speed\",\n \"Qianfan-Chinese-Llama-2-13B\",\n \"ERNIE-3.5-8K\",\n \"BLOOMZ-7B\",\n \"Qianfan-Chinese-Llama-2-7B\",\n \"XuanYuan-70B-Chat-4bit\",\n \"AquilaChat-7B\",\n \"ERNIE-Bot-4\",\n \"Llama-2-13b-chat\",\n \"ChatGLM2-6B-32K\",\n \"ERNIE-Bot\",\n \"ERNIE-Speed-128k\",\n \"ERNIE-4.0-8K\",\n \"Qianfan-BLOOMZ-7B-compressed\",\n \"ERNIE Speed\",\n \"Llama-2-7b-chat\",\n \"Mixtral-8x7B-Instruct\",\n \"ERNIE 3.5\",\n \"ERNIE Speed-AppBuilder\",\n \"ERNIE-Speed-8K\",\n \"Yi-34B-Chat\",\n ],\n info=\"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint\",\n value=\"ERNIE-4.0-8K\",\n ),\n SecretStrInput(\n name=\"qianfan_ak\",\n display_name=\"Qianfan Ak\",\n info=\"which you could get from https://cloud.baidu.com/product/wenxinworkshop\",\n ),\n SecretStrInput(\n name=\"qianfan_sk\",\n display_name=\"Qianfan Sk\",\n info=\"which you could get from https://cloud.baidu.com/product/wenxinworkshop\",\n ),\n FloatInput(\n name=\"top_p\",\n display_name=\"Top p\",\n info=\"Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo\",\n value=0.8,\n advanced=True,\n ),\n FloatInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n info=\"Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo\",\n value=0.95,\n ),\n FloatInput(\n name=\"penalty_score\",\n display_name=\"Penalty Score\",\n info=\"Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo\",\n value=1.0,\n advanced=True,\n ),\n MessageTextInput(\n name=\"endpoint\", display_name=\"Endpoint\", info=\"Endpoint of the Qianfan LLM, required if custom model used.\"\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n model = self.model\n qianfan_ak = self.qianfan_ak\n qianfan_sk = self.qianfan_sk\n top_p = self.top_p\n temperature = self.temperature\n penalty_score = self.penalty_score\n endpoint = self.endpoint\n\n try:\n kwargs = {\n \"model\": model,\n \"qianfan_ak\": qianfan_ak or None,\n \"qianfan_sk\": qianfan_sk or None,\n \"top_p\": top_p,\n \"temperature\": temperature,\n \"penalty_score\": penalty_score,\n }\n\n if endpoint: # Only add endpoint if it has a value\n kwargs[\"endpoint\"] = endpoint\n\n output = QianfanChatEndpoint(**kwargs)\n\n except Exception as e:\n msg = \"Could not connect to Baidu Qianfan API.\"\n raise ValueError(msg) from e\n\n return output\n"},"endpoint":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Endpoint","dynamic":false,"info":"Endpoint of the Qianfan LLM, required if custom model used.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"endpoint","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_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model Name","dynamic":false,"external_options":{},"info":"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint","name":"model","options":["EB-turbo-AppBuilder","Llama-2-70b-chat","ERNIE-Bot-turbo-AI","ERNIE-Lite-8K-0308","ERNIE-Speed","Qianfan-Chinese-Llama-2-13B","ERNIE-3.5-8K","BLOOMZ-7B","Qianfan-Chinese-Llama-2-7B","XuanYuan-70B-Chat-4bit","AquilaChat-7B","ERNIE-Bot-4","Llama-2-13b-chat","ChatGLM2-6B-32K","ERNIE-Bot","ERNIE-Speed-128k","ERNIE-4.0-8K","Qianfan-BLOOMZ-7B-compressed","ERNIE Speed","Llama-2-7b-chat","Mixtral-8x7B-Instruct","ERNIE 3.5","ERNIE Speed-AppBuilder","ERNIE-Speed-8K","Yi-34B-Chat"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"ERNIE-4.0-8K"},"penalty_score":{"_input_type":"FloatInput","advanced":true,"display_name":"Penalty Score","dynamic":false,"info":"Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo","list":false,"list_add_label":"Add More","name":"penalty_score","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":1.0},"qianfan_ak":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Qianfan Ak","dynamic":false,"info":"which you could get from https://cloud.baidu.com/product/wenxinworkshop","input_types":[],"load_from_db":true,"name":"qianfan_ak","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"qianfan_sk":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Qianfan Sk","dynamic":false,"info":"which you could get from https://cloud.baidu.com/product/wenxinworkshop","input_types":[],"load_from_db":true,"name":"qianfan_sk","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"FloatInput","advanced":false,"display_name":"Temperature","dynamic":false,"info":"Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo","list":false,"list_add_label":"Add More","name":"temperature","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.95},"top_p":{"_input_type":"FloatInput","advanced":true,"display_name":"Top p","dynamic":false,"info":"Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo","list":false,"list_add_label":"Add More","name":"top_p","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.8}},"tool_mode":false}}],["bing",{"BingSearchAPI":{"base_classes":["DataFrame","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Call the Bing Search API.","display_name":"Bing Search API","documentation":"","edited":false,"field_order":["bing_subscription_key","input_value","bing_search_url","k"],"frozen":false,"icon":"Bing","legacy":false,"metadata":{"code_hash":"84334607b325","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.bing.bing_search_api.BingSearchAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"fetch_content_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","bing_search_url":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Bing Search URL","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"bing_search_url","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":""},"bing_subscription_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bing Subscription Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bing_subscription_key","override_skip":false,"password":true,"placeholder":"","required":false,"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":"from typing import cast\n\nfrom langchain_community.tools.bing_search import BingSearchResults\nfrom langchain_community.utilities import BingSearchAPIWrapper\n\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.field_typing import Tool\nfrom lfx.inputs.inputs import IntInput, MessageTextInput, MultilineInput, SecretStrInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.template.field.base import Output\n\n\nclass BingSearchAPIComponent(LCToolComponent):\n display_name = \"Bing Search API\"\n description = \"Call the Bing Search API.\"\n name = \"BingSearchAPI\"\n icon = \"Bing\"\n\n inputs = [\n SecretStrInput(name=\"bing_subscription_key\", display_name=\"Bing Subscription Key\"),\n MultilineInput(\n name=\"input_value\",\n display_name=\"Input\",\n ),\n MessageTextInput(name=\"bing_search_url\", display_name=\"Bing Search URL\", advanced=True),\n IntInput(name=\"k\", display_name=\"Number of results\", value=4, required=True),\n ]\n\n outputs = [\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"fetch_content_dataframe\"),\n Output(display_name=\"Tool\", name=\"tool\", method=\"build_tool\"),\n ]\n\n def run_model(self) -> DataFrame:\n return self.fetch_content_dataframe()\n\n def fetch_content(self) -> list[Data]:\n if self.bing_search_url:\n wrapper = BingSearchAPIWrapper(\n bing_search_url=self.bing_search_url, bing_subscription_key=self.bing_subscription_key\n )\n else:\n wrapper = BingSearchAPIWrapper(bing_subscription_key=self.bing_subscription_key)\n results = wrapper.results(query=self.input_value, num_results=self.k)\n data = [Data(data=result, text=result[\"snippet\"]) for result in results]\n self.status = data\n return data\n\n def fetch_content_dataframe(self) -> DataFrame:\n data = self.fetch_content()\n return DataFrame(data)\n\n def build_tool(self) -> Tool:\n if self.bing_search_url:\n wrapper = BingSearchAPIWrapper(\n bing_search_url=self.bing_search_url, bing_subscription_key=self.bing_subscription_key\n )\n else:\n wrapper = BingSearchAPIWrapper(bing_subscription_key=self.bing_subscription_key)\n return cast(\"Tool\", BingSearchResults(api_wrapper=wrapper, num_results=self.k))\n"},"input_value":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"input_value","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":""},"k":{"_input_type":"IntInput","advanced":false,"display_name":"Number of results","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"k","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int","value":4}},"tool_mode":false}}],["cassandra",{"Cassandra":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Cassandra Vector Store with search capabilities","display_name":"Cassandra","documentation":"https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/cassandra","edited":false,"field_order":["database_ref","username","token","keyspace","table_name","ttl_seconds","batch_size","setup_mode","cluster_kwargs","ingest_data","search_query","should_cache_vector_store","embedding","number_of_results","search_type","search_score_threshold","search_filter","body_search","enable_body_search"],"frozen":false,"icon":"Cassandra","legacy":false,"metadata":{"code_hash":"833f277daab7","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null},{"name":"cassio","version":null}],"total_dependencies":3},"module":"lfx.components.cassandra.cassandra.CassandraVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","batch_size":{"_input_type":"IntInput","advanced":true,"display_name":"Batch Size","dynamic":false,"info":"Optional number of data to process in a single batch.","list":false,"list_add_label":"Add More","name":"batch_size","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":16},"body_search":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Search Body","dynamic":false,"info":"Document textual search terms to apply to the search query.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"body_search","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":""},"cluster_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Cluster arguments","dynamic":false,"info":"Optional dictionary of additional keyword arguments for the Cassandra cluster.","list":true,"list_add_label":"Add More","name":"cluster_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","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":"from langchain_community.vectorstores import Cassandra\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.inputs.inputs import BoolInput, DictInput, FloatInput\nfrom lfx.io import (\n DropdownInput,\n HandleInput,\n IntInput,\n MessageTextInput,\n SecretStrInput,\n)\nfrom lfx.schema.data import Data\n\n\nclass CassandraVectorStoreComponent(LCVectorStoreComponent):\n display_name = \"Cassandra\"\n description = \"Cassandra Vector Store with search capabilities\"\n documentation = \"https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/cassandra\"\n name = \"Cassandra\"\n icon = \"Cassandra\"\n\n inputs = [\n MessageTextInput(\n name=\"database_ref\",\n display_name=\"Contact Points / Astra Database ID\",\n info=\"Contact points for the database (or Astra DB database ID)\",\n required=True,\n ),\n MessageTextInput(\n name=\"username\", display_name=\"Username\", info=\"Username for the database (leave empty for Astra DB).\"\n ),\n SecretStrInput(\n name=\"token\",\n display_name=\"Password / Astra DB Token\",\n info=\"User password for the database (or Astra DB token).\",\n required=True,\n ),\n MessageTextInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Table Keyspace (or Astra DB namespace).\",\n required=True,\n ),\n MessageTextInput(\n name=\"table_name\",\n display_name=\"Table Name\",\n info=\"The name of the table (or Astra DB collection) where vectors will be stored.\",\n required=True,\n ),\n IntInput(\n name=\"ttl_seconds\",\n display_name=\"TTL Seconds\",\n info=\"Optional time-to-live for the added texts.\",\n advanced=True,\n ),\n IntInput(\n name=\"batch_size\",\n display_name=\"Batch Size\",\n info=\"Optional number of data to process in a single batch.\",\n value=16,\n advanced=True,\n ),\n DropdownInput(\n name=\"setup_mode\",\n display_name=\"Setup Mode\",\n info=\"Configuration mode for setting up the Cassandra table, with options like 'Sync', 'Async', or 'Off'.\",\n options=[\"Sync\", \"Async\", \"Off\"],\n value=\"Sync\",\n advanced=True,\n ),\n DictInput(\n name=\"cluster_kwargs\",\n display_name=\"Cluster arguments\",\n info=\"Optional dictionary of additional keyword arguments for the Cassandra cluster.\",\n advanced=True,\n list=True,\n ),\n *LCVectorStoreComponent.inputs,\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n value=4,\n advanced=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n list=True,\n ),\n MessageTextInput(\n name=\"body_search\",\n display_name=\"Search Body\",\n info=\"Document textual search terms to apply to the search query.\",\n advanced=True,\n ),\n BoolInput(\n name=\"enable_body_search\",\n display_name=\"Enable Body Search\",\n info=\"Flag to enable body search. This must be enabled BEFORE the table is created.\",\n value=False,\n advanced=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self) -> Cassandra:\n try:\n import cassio\n from langchain_community.utilities.cassandra import SetupMode\n except ImportError as e:\n msg = \"Could not import cassio integration package. Please install it with `pip install cassio`.\"\n raise ImportError(msg) from e\n\n from uuid import UUID\n\n database_ref = self.database_ref\n\n try:\n UUID(self.database_ref)\n is_astra = True\n except ValueError:\n is_astra = False\n if \",\" in self.database_ref:\n # use a copy because we can't change the type of the parameter\n database_ref = self.database_ref.split(\",\")\n\n if is_astra:\n cassio.init(\n database_id=database_ref,\n token=self.token,\n cluster_kwargs=self.cluster_kwargs,\n )\n else:\n cassio.init(\n contact_points=database_ref,\n username=self.username,\n password=self.token,\n cluster_kwargs=self.cluster_kwargs,\n )\n\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n body_index_options = [(\"index_analyzer\", \"STANDARD\")] if self.enable_body_search else None\n\n if self.setup_mode == \"Off\":\n setup_mode = SetupMode.OFF\n elif self.setup_mode == \"Sync\":\n setup_mode = SetupMode.SYNC\n else:\n setup_mode = SetupMode.ASYNC\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n table = Cassandra.from_documents(\n documents=documents,\n embedding=self.embedding,\n table_name=self.table_name,\n keyspace=self.keyspace,\n ttl_seconds=self.ttl_seconds or None,\n batch_size=self.batch_size,\n body_index_options=body_index_options,\n )\n else:\n self.log(\"No documents to add to the Vector Store.\")\n table = Cassandra(\n embedding=self.embedding,\n table_name=self.table_name,\n keyspace=self.keyspace,\n ttl_seconds=self.ttl_seconds or None,\n body_index_options=body_index_options,\n setup_mode=setup_mode,\n )\n return table\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def search_documents(self) -> list[Data]:\n vector_store = self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n self.log(f\"Search args: {search_args}\")\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n except KeyError as e:\n if \"content\" in str(e):\n msg = (\n \"You should ingest data through Langflow (or LangChain) to query it in Langflow. \"\n \"Your collection does not contain a field name 'content'.\"\n )\n raise ValueError(msg) from e\n raise\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.status = data\n return data\n return []\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n if self.body_search:\n if not self.enable_body_search:\n msg = \"You should enable body search when creating the table to search the body field.\"\n raise ValueError(msg)\n args[\"body_search\"] = self.body_search\n return args\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"},"database_ref":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Contact Points / Astra Database ID","dynamic":false,"info":"Contact points for the database (or Astra DB database ID)","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"database_ref","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"enable_body_search":{"_input_type":"BoolInput","advanced":true,"display_name":"Enable Body Search","dynamic":false,"info":"Flag to enable body search. This must be enabled BEFORE the table is created.","list":false,"list_add_label":"Add More","name":"enable_body_search","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"keyspace":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Keyspace","dynamic":false,"info":"Table Keyspace (or Astra DB namespace).","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"keyspace","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"search_filter":{"_input_type":"DictInput","advanced":true,"display_name":"Search Metadata Filter","dynamic":false,"info":"Optional dictionary of filters to apply to the search query.","list":true,"list_add_label":"Add More","name":"search_filter","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"search_score_threshold":{"_input_type":"FloatInput","advanced":true,"display_name":"Search Score Threshold","dynamic":false,"info":"Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')","list":false,"list_add_label":"Add More","name":"search_score_threshold","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.0},"search_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Type","dynamic":false,"external_options":{},"info":"Search type to use","name":"search_type","options":["Similarity","Similarity with score threshold","MMR (Max Marginal Relevance)"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Similarity"},"setup_mode":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Setup Mode","dynamic":false,"external_options":{},"info":"Configuration mode for setting up the Cassandra table, with options like 'Sync', 'Async', or 'Off'.","name":"setup_mode","options":["Sync","Async","Off"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Sync"},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"table_name":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Table Name","dynamic":false,"info":"The name of the table (or Astra DB collection) where vectors will be stored.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"table_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password / Astra DB Token","dynamic":false,"info":"User password for the database (or Astra DB token).","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"ttl_seconds":{"_input_type":"IntInput","advanced":true,"display_name":"TTL Seconds","dynamic":false,"info":"Optional time-to-live for the added texts.","list":false,"list_add_label":"Add More","name":"ttl_seconds","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":""},"username":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Username","dynamic":false,"info":"Username for the database (leave empty for Astra DB).","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","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":""}},"tool_mode":false},"CassandraChatMemory":{"base_classes":["Memory"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Retrieves and store chat messages from Apache Cassandra.","display_name":"Cassandra Chat Memory","documentation":"","edited":false,"field_order":["database_ref","username","token","keyspace","table_name","session_id","cluster_kwargs"],"frozen":false,"icon":"Cassandra","legacy":false,"metadata":{"code_hash":"f6497182984e","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_community","version":"0.3.21"},{"name":"cassio","version":null}],"total_dependencies":3},"module":"lfx.components.cassandra.cassandra_chat.CassandraChatMemory"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Memory","group_outputs":false,"method":"build_message_history","name":"memory","selected":"Memory","tool_mode":true,"types":["Memory"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","cluster_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Cluster arguments","dynamic":false,"info":"Optional dictionary of additional keyword arguments for the Cassandra cluster.","list":true,"list_add_label":"Add More","name":"cluster_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","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":"from lfx.base.memory.model import LCChatMemoryComponent\nfrom lfx.field_typing.constants import Memory\nfrom lfx.inputs.inputs import DictInput, MessageTextInput, SecretStrInput\n\n\nclass CassandraChatMemory(LCChatMemoryComponent):\n display_name = \"Cassandra Chat Memory\"\n description = \"Retrieves and store chat messages from Apache Cassandra.\"\n name = \"CassandraChatMemory\"\n icon = \"Cassandra\"\n\n inputs = [\n MessageTextInput(\n name=\"database_ref\",\n display_name=\"Contact Points / Astra Database ID\",\n info=\"Contact points for the database (or Astra DB database ID)\",\n required=True,\n ),\n MessageTextInput(\n name=\"username\", display_name=\"Username\", info=\"Username for the database (leave empty for Astra DB).\"\n ),\n SecretStrInput(\n name=\"token\",\n display_name=\"Password / Astra DB Token\",\n info=\"User password for the database (or Astra DB token).\",\n required=True,\n ),\n MessageTextInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Table Keyspace (or Astra DB namespace).\",\n required=True,\n ),\n MessageTextInput(\n name=\"table_name\",\n display_name=\"Table Name\",\n info=\"The name of the table (or Astra DB collection) where vectors will be stored.\",\n required=True,\n ),\n MessageTextInput(\n name=\"session_id\", display_name=\"Session ID\", info=\"Session ID for the message.\", advanced=True\n ),\n DictInput(\n name=\"cluster_kwargs\",\n display_name=\"Cluster arguments\",\n info=\"Optional dictionary of additional keyword arguments for the Cassandra cluster.\",\n advanced=True,\n is_list=True,\n ),\n ]\n\n def build_message_history(self) -> Memory:\n from langchain_community.chat_message_histories import CassandraChatMessageHistory\n\n try:\n import cassio\n except ImportError as e:\n msg = \"Could not import cassio integration package. Please install it with `pip install cassio`.\"\n raise ImportError(msg) from e\n\n from uuid import UUID\n\n database_ref = self.database_ref\n\n try:\n UUID(self.database_ref)\n is_astra = True\n except ValueError:\n is_astra = False\n if \",\" in self.database_ref:\n # use a copy because we can't change the type of the parameter\n database_ref = self.database_ref.split(\",\")\n\n if is_astra:\n cassio.init(\n database_id=database_ref,\n token=self.token,\n cluster_kwargs=self.cluster_kwargs,\n )\n else:\n cassio.init(\n contact_points=database_ref,\n username=self.username,\n password=self.token,\n cluster_kwargs=self.cluster_kwargs,\n )\n\n return CassandraChatMessageHistory(\n session_id=self.session_id,\n table_name=self.table_name,\n keyspace=self.keyspace,\n )\n"},"database_ref":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Contact Points / Astra Database ID","dynamic":false,"info":"Contact points for the database (or Astra DB database ID)","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"database_ref","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"keyspace":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Keyspace","dynamic":false,"info":"Table Keyspace (or Astra DB namespace).","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"keyspace","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"session_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Session ID","dynamic":false,"info":"Session ID for the message.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"session_id","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":""},"table_name":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Table Name","dynamic":false,"info":"The name of the table (or Astra DB collection) where vectors will be stored.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"table_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password / Astra DB Token","dynamic":false,"info":"User password for the database (or Astra DB token).","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Username","dynamic":false,"info":"Username for the database (leave empty for Astra DB).","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","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":""}},"tool_mode":false},"CassandraGraph":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Cassandra Graph Vector Store","display_name":"Cassandra Graph","documentation":"","edited":false,"field_order":["database_ref","username","token","keyspace","table_name","setup_mode","cluster_kwargs","ingest_data","search_query","should_cache_vector_store","embedding","number_of_results","search_type","depth","search_score_threshold","search_filter"],"frozen":false,"icon":"Cassandra","legacy":false,"metadata":{"code_hash":"26c63f80745e","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null},{"name":"cassio","version":null}],"total_dependencies":3},"module":"lfx.components.cassandra.cassandra_graph.CassandraGraphVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","cluster_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Cluster arguments","dynamic":false,"info":"Optional dictionary of additional keyword arguments for the Cassandra cluster.","list":true,"list_add_label":"Add More","name":"cluster_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","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":"from uuid import UUID\n\nfrom langchain_community.graph_vectorstores import CassandraGraphVectorStore\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.inputs.inputs import DictInput, FloatInput\nfrom lfx.io import (\n DropdownInput,\n HandleInput,\n IntInput,\n MessageTextInput,\n SecretStrInput,\n)\nfrom lfx.schema.data import Data\n\n\nclass CassandraGraphVectorStoreComponent(LCVectorStoreComponent):\n display_name = \"Cassandra Graph\"\n description = \"Cassandra Graph Vector Store\"\n name = \"CassandraGraph\"\n icon = \"Cassandra\"\n\n inputs = [\n MessageTextInput(\n name=\"database_ref\",\n display_name=\"Contact Points / Astra Database ID\",\n info=\"Contact points for the database (or Astra DB database ID)\",\n required=True,\n ),\n MessageTextInput(\n name=\"username\", display_name=\"Username\", info=\"Username for the database (leave empty for Astra DB).\"\n ),\n SecretStrInput(\n name=\"token\",\n display_name=\"Password / Astra DB Token\",\n info=\"User password for the database (or Astra DB token).\",\n required=True,\n ),\n MessageTextInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Table Keyspace (or Astra DB namespace).\",\n required=True,\n ),\n MessageTextInput(\n name=\"table_name\",\n display_name=\"Table Name\",\n info=\"The name of the table (or Astra DB collection) where vectors will be stored.\",\n required=True,\n ),\n DropdownInput(\n name=\"setup_mode\",\n display_name=\"Setup Mode\",\n info=\"Configuration mode for setting up the Cassandra table, with options like 'Sync' or 'Off'.\",\n options=[\"Sync\", \"Off\"],\n value=\"Sync\",\n advanced=True,\n ),\n DictInput(\n name=\"cluster_kwargs\",\n display_name=\"Cluster arguments\",\n info=\"Optional dictionary of additional keyword arguments for the Cassandra cluster.\",\n advanced=True,\n list=True,\n ),\n *LCVectorStoreComponent.inputs,\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n value=4,\n advanced=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\n \"Traversal\",\n \"MMR traversal\",\n \"Similarity\",\n \"Similarity with score threshold\",\n \"MMR (Max Marginal Relevance)\",\n ],\n value=\"Traversal\",\n advanced=True,\n ),\n IntInput(\n name=\"depth\",\n display_name=\"Depth of traversal\",\n info=\"The maximum depth of edges to traverse. (when using 'Traversal' or 'MMR traversal')\",\n value=1,\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n list=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self) -> CassandraGraphVectorStore:\n try:\n import cassio\n from langchain_community.utilities.cassandra import SetupMode\n except ImportError as e:\n msg = \"Could not import cassio integration package. Please install it with `pip install cassio`.\"\n raise ImportError(msg) from e\n\n database_ref = self.database_ref\n\n try:\n UUID(self.database_ref)\n is_astra = True\n except ValueError:\n is_astra = False\n if \",\" in self.database_ref:\n # use a copy because we can't change the type of the parameter\n database_ref = self.database_ref.split(\",\")\n\n if is_astra:\n cassio.init(\n database_id=database_ref,\n token=self.token,\n cluster_kwargs=self.cluster_kwargs,\n )\n else:\n cassio.init(\n contact_points=database_ref,\n username=self.username,\n password=self.token,\n cluster_kwargs=self.cluster_kwargs,\n )\n\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n setup_mode = SetupMode.OFF if self.setup_mode == \"Off\" else SetupMode.SYNC\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n store = CassandraGraphVectorStore.from_documents(\n documents=documents,\n embedding=self.embedding,\n node_table=self.table_name,\n keyspace=self.keyspace,\n )\n else:\n self.log(\"No documents to add to the Vector Store.\")\n store = CassandraGraphVectorStore(\n embedding=self.embedding,\n node_table=self.table_name,\n keyspace=self.keyspace,\n setup_mode=setup_mode,\n )\n return store\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity\":\n return \"similarity\"\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n if self.search_type == \"MMR Traversal\":\n return \"mmr_traversal\"\n return \"traversal\"\n\n def search_documents(self) -> list[Data]:\n vector_store = self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n self.log(f\"Search args: {search_args}\")\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n except KeyError as e:\n if \"content\" in str(e):\n msg = (\n \"You should ingest data through Langflow (or LangChain) to query it in Langflow. \"\n \"Your collection does not contain a field name 'content'.\"\n )\n raise ValueError(msg) from e\n raise\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.status = data\n return data\n return []\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n \"depth\": self.depth,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n return args\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"},"database_ref":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Contact Points / Astra Database ID","dynamic":false,"info":"Contact points for the database (or Astra DB database ID)","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"database_ref","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"depth":{"_input_type":"IntInput","advanced":true,"display_name":"Depth of traversal","dynamic":false,"info":"The maximum depth of edges to traverse. (when using 'Traversal' or 'MMR traversal')","list":false,"list_add_label":"Add More","name":"depth","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":1},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"keyspace":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Keyspace","dynamic":false,"info":"Table Keyspace (or Astra DB namespace).","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"keyspace","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"search_filter":{"_input_type":"DictInput","advanced":true,"display_name":"Search Metadata Filter","dynamic":false,"info":"Optional dictionary of filters to apply to the search query.","list":true,"list_add_label":"Add More","name":"search_filter","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"search_score_threshold":{"_input_type":"FloatInput","advanced":true,"display_name":"Search Score Threshold","dynamic":false,"info":"Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')","list":false,"list_add_label":"Add More","name":"search_score_threshold","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.0},"search_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Type","dynamic":false,"external_options":{},"info":"Search type to use","name":"search_type","options":["Traversal","MMR traversal","Similarity","Similarity with score threshold","MMR (Max Marginal Relevance)"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Traversal"},"setup_mode":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Setup Mode","dynamic":false,"external_options":{},"info":"Configuration mode for setting up the Cassandra table, with options like 'Sync' or 'Off'.","name":"setup_mode","options":["Sync","Off"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Sync"},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"table_name":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Table Name","dynamic":false,"info":"The name of the table (or Astra DB collection) where vectors will be stored.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"table_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password / Astra DB Token","dynamic":false,"info":"User password for the database (or Astra DB token).","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Username","dynamic":false,"info":"Username for the database (leave empty for Astra DB).","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","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":""}},"tool_mode":false}}],["chroma",{"Chroma":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Chroma Vector Store with search capabilities","display_name":"Chroma DB","documentation":"","edited":false,"field_order":["collection_name","persist_directory","ingest_data","search_query","should_cache_vector_store","embedding","chroma_server_cors_allow_origins","chroma_server_host","chroma_server_http_port","chroma_server_grpc_port","chroma_server_ssl_enabled","allow_duplicates","search_type","number_of_results","limit"],"frozen":false,"icon":"Chroma","legacy":false,"metadata":{"code_hash":"82d38624f19a","dependencies":{"dependencies":[{"name":"chromadb","version":"1.3.5"},{"name":"langchain_chroma","version":"0.2.6"},{"name":"typing_extensions","version":"4.15.0"},{"name":"lfx","version":null},{"name":"langchain_community","version":"0.3.21"}],"total_dependencies":5},"module":"lfx.components.chroma.chroma.ChromaVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","allow_duplicates":{"_input_type":"BoolInput","advanced":true,"display_name":"Allow Duplicates","dynamic":false,"info":"If false, will not add documents that are already in the Vector Store.","list":false,"list_add_label":"Add More","name":"allow_duplicates","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"chroma_server_cors_allow_origins":{"_input_type":"StrInput","advanced":true,"display_name":"Server CORS Allow Origins","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"chroma_server_cors_allow_origins","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"chroma_server_grpc_port":{"_input_type":"IntInput","advanced":true,"display_name":"Server gRPC Port","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"chroma_server_grpc_port","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":""},"chroma_server_host":{"_input_type":"StrInput","advanced":true,"display_name":"Server Host","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"chroma_server_host","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"chroma_server_http_port":{"_input_type":"IntInput","advanced":true,"display_name":"Server HTTP Port","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"chroma_server_http_port","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":""},"chroma_server_ssl_enabled":{"_input_type":"BoolInput","advanced":true,"display_name":"Server SSL Enabled","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"chroma_server_ssl_enabled","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"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":"from copy import deepcopy\nfrom typing import TYPE_CHECKING\n\nfrom chromadb.config import Settings\nfrom langchain_chroma import Chroma\nfrom typing_extensions import override\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.base.vectorstores.utils import chroma_collection_to_data\nfrom lfx.inputs.inputs import BoolInput, DropdownInput, HandleInput, IntInput, StrInput\nfrom lfx.schema.data import Data\n\nif TYPE_CHECKING:\n from lfx.schema.dataframe import DataFrame\n\n\nclass ChromaVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"Chroma Vector Store with search capabilities.\"\"\"\n\n display_name: str = \"Chroma DB\"\n description: str = \"Chroma Vector Store with search capabilities\"\n name = \"Chroma\"\n icon = \"Chroma\"\n\n inputs = [\n StrInput(\n name=\"collection_name\",\n display_name=\"Collection Name\",\n value=\"langflow\",\n ),\n StrInput(\n name=\"persist_directory\",\n display_name=\"Persist Directory\",\n ),\n *LCVectorStoreComponent.inputs,\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n StrInput(\n name=\"chroma_server_cors_allow_origins\",\n display_name=\"Server CORS Allow Origins\",\n advanced=True,\n ),\n StrInput(\n name=\"chroma_server_host\",\n display_name=\"Server Host\",\n advanced=True,\n ),\n IntInput(\n name=\"chroma_server_http_port\",\n display_name=\"Server HTTP Port\",\n advanced=True,\n ),\n IntInput(\n name=\"chroma_server_grpc_port\",\n display_name=\"Server gRPC Port\",\n advanced=True,\n ),\n BoolInput(\n name=\"chroma_server_ssl_enabled\",\n display_name=\"Server SSL Enabled\",\n advanced=True,\n ),\n BoolInput(\n name=\"allow_duplicates\",\n display_name=\"Allow Duplicates\",\n advanced=True,\n info=\"If false, will not add documents that are already in the Vector Store.\",\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n options=[\"Similarity\", \"MMR\"],\n value=\"Similarity\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=10,\n ),\n IntInput(\n name=\"limit\",\n display_name=\"Limit\",\n advanced=True,\n info=\"Limit the number of records to compare when Allow Duplicates is False.\",\n ),\n ]\n\n @override\n @check_cached_vector_store\n def build_vector_store(self) -> Chroma:\n \"\"\"Builds the Chroma object.\"\"\"\n try:\n from chromadb import Client\n from langchain_chroma import Chroma\n except ImportError as e:\n msg = \"Could not import Chroma integration package. Please install it with `pip install langchain-chroma`.\"\n raise ImportError(msg) from e\n # Chroma settings\n chroma_settings = None\n client = None\n if self.chroma_server_host:\n chroma_settings = Settings(\n chroma_server_cors_allow_origins=self.chroma_server_cors_allow_origins or [],\n chroma_server_host=self.chroma_server_host,\n chroma_server_http_port=self.chroma_server_http_port or None,\n chroma_server_grpc_port=self.chroma_server_grpc_port or None,\n chroma_server_ssl_enabled=self.chroma_server_ssl_enabled,\n )\n client = Client(settings=chroma_settings)\n\n # Check persist_directory and expand it if it is a relative path\n persist_directory = self.resolve_path(self.persist_directory) if self.persist_directory is not None else None\n\n chroma = Chroma(\n persist_directory=persist_directory,\n client=client,\n embedding_function=self.embedding,\n collection_name=self.collection_name,\n )\n\n self._add_documents_to_vector_store(chroma)\n limit = int(self.limit) if self.limit is not None and str(self.limit).strip() else None\n self.status = chroma_collection_to_data(chroma.get(limit=limit))\n return chroma\n\n def _add_documents_to_vector_store(self, vector_store: \"Chroma\") -> None:\n \"\"\"Adds documents to the Vector Store.\"\"\"\n ingest_data: list | Data | DataFrame = self.ingest_data\n if not ingest_data:\n self.status = \"\"\n return\n\n # Convert DataFrame to Data if needed using parent's method\n ingest_data = self._prepare_ingest_data()\n\n stored_documents_without_id = []\n if self.allow_duplicates:\n stored_data = []\n else:\n limit = int(self.limit) if self.limit is not None and str(self.limit).strip() else None\n stored_data = chroma_collection_to_data(vector_store.get(limit=limit))\n for value in deepcopy(stored_data):\n del value.id\n stored_documents_without_id.append(value)\n\n documents = []\n for _input in ingest_data or []:\n if isinstance(_input, Data):\n if _input not in stored_documents_without_id:\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents and self.embedding is not None:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n # Filter complex metadata to prevent ChromaDB errors\n try:\n from langchain_community.vectorstores.utils import filter_complex_metadata\n\n filtered_documents = filter_complex_metadata(documents)\n vector_store.add_documents(filtered_documents)\n except ImportError:\n self.log(\"Warning: Could not import filter_complex_metadata. Adding documents without filtering.\")\n vector_store.add_documents(documents)\n else:\n self.log(\"No documents to add to the Vector Store.\")\n"},"collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Collection Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"collection_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"langflow"},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"limit":{"_input_type":"IntInput","advanced":true,"display_name":"Limit","dynamic":false,"info":"Limit the number of records to compare when Allow Duplicates is False.","list":false,"list_add_label":"Add More","name":"limit","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":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":10},"persist_directory":{"_input_type":"StrInput","advanced":false,"display_name":"Persist Directory","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"persist_directory","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"search_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Type","dynamic":false,"external_options":{},"info":"","name":"search_type","options":["Similarity","MMR"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Similarity"},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false}}],["cleanlab",{"CleanlabEvaluator":{"base_classes":["float","Message","number"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Evaluates any LLM response using Cleanlab and outputs trust score and explanation.","display_name":"Cleanlab Evaluator","documentation":"","edited":false,"field_order":["system_prompt","prompt","response","api_key","model","quality_preset"],"frozen":false,"icon":"Cleanlab","legacy":false,"metadata":{"code_hash":"06963c804ffe","dependencies":{"dependencies":[{"name":"cleanlab_tlm","version":"1.1.39"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.cleanlab.cleanlab_evaluator.CleanlabEvaluator"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Response","group_outputs":false,"method":"pass_response","name":"response_passthrough","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Trust Score","group_outputs":false,"method":"get_score","name":"score","selected":"number","tool_mode":true,"types":["number","float"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Explanation","group_outputs":false,"method":"get_explanation","name":"explanation","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Cleanlab API Key","dynamic":false,"info":"Your Cleanlab 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":"from cleanlab_tlm import TLM\n\nfrom lfx.custom import Component\nfrom lfx.io import (\n DropdownInput,\n MessageTextInput,\n Output,\n SecretStrInput,\n)\nfrom lfx.schema.message import Message\n\n\nclass CleanlabEvaluator(Component):\n \"\"\"A component that evaluates the trustworthiness of LLM responses using Cleanlab.\n\n This component takes a prompt and response pair, along with optional system instructions,\n and uses Cleanlab's evaluation algorithms to generate a trust score and explanation.\n\n Inputs:\n - system_prompt (MessageTextInput): Optional system-level instructions prepended to the user prompt.\n - prompt (MessageTextInput): The user's prompt or query sent to the LLM.\n - response (MessageTextInput): The response generated by the LLM to be evaluated. This should come from the\n LLM component, i.e. OpenAI, Gemini, etc.\n - api_key (SecretStrInput): Your Cleanlab API key.\n - model (DropdownInput): The model used by Cleanlab to evaluate the response (can differ from the\n generation model).\n - quality_preset (DropdownInput): Tradeoff setting for accuracy vs. speed and cost. Higher presets are\n slower but more accurate.\n\n Outputs:\n - response_passthrough (Message): The original response, passed through for downstream use.\n - score (number): A float between 0 and 1 indicating Cleanlab's trustworthiness score for the response.\n - explanation (Message): A textual explanation of why the response received its score.\n\n This component works well in conjunction with the CleanlabRemediator to create a complete trust evaluation\n and remediation pipeline.\n\n More details on the evaluation metrics can be found here: https://help.cleanlab.ai/tlm/tutorials/tlm/\n \"\"\"\n\n display_name = \"Cleanlab Evaluator\"\n description = \"Evaluates any LLM response using Cleanlab and outputs trust score and explanation.\"\n icon = \"Cleanlab\"\n name = \"CleanlabEvaluator\"\n\n inputs = [\n MessageTextInput(\n name=\"system_prompt\",\n display_name=\"System Message\",\n info=\"System-level instructions prepended to the user query.\",\n value=\"\",\n ),\n MessageTextInput(\n name=\"prompt\",\n display_name=\"Prompt\",\n info=\"The user's query to the model.\",\n required=True,\n ),\n MessageTextInput(\n name=\"response\",\n display_name=\"Response\",\n info=\"The response to the user's query.\",\n required=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Cleanlab API Key\",\n info=\"Your Cleanlab API key.\",\n required=True,\n ),\n DropdownInput(\n name=\"model\",\n display_name=\"Cleanlab Evaluation Model\",\n options=[\n \"gpt-4.1\",\n \"gpt-4.1-mini\",\n \"gpt-4.1-nano\",\n \"o4-mini\",\n \"o3\",\n \"gpt-4.5-preview\",\n \"gpt-4o-mini\",\n \"gpt-4o\",\n \"o3-mini\",\n \"o1\",\n \"o1-mini\",\n \"gpt-4\",\n \"gpt-3.5-turbo-16k\",\n \"claude-3.7-sonnet\",\n \"claude-3.5-sonnet-v2\",\n \"claude-3.5-sonnet\",\n \"claude-3.5-haiku\",\n \"claude-3-haiku\",\n \"nova-micro\",\n \"nova-lite\",\n \"nova-pro\",\n ],\n info=\"The model Cleanlab uses to evaluate the response. This does NOT need to be the same model that \"\n \"generated the response.\",\n value=\"gpt-4o-mini\",\n required=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"quality_preset\",\n display_name=\"Quality Preset\",\n options=[\"base\", \"low\", \"medium\", \"high\", \"best\"],\n value=\"medium\",\n info=\"This determines the accuracy, latency, and cost of the evaluation. Higher quality is generally \"\n \"slower but more accurate.\",\n required=True,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Response\",\n name=\"response_passthrough\",\n method=\"pass_response\",\n types=[\"Message\"],\n ),\n Output(display_name=\"Trust Score\", name=\"score\", method=\"get_score\", types=[\"number\"]),\n Output(\n display_name=\"Explanation\",\n name=\"explanation\",\n method=\"get_explanation\",\n types=[\"Message\"],\n ),\n ]\n\n def _evaluate_once(self):\n if not hasattr(self, \"_cached_result\"):\n full_prompt = f\"{self.system_prompt}\\n\\n{self.prompt}\" if self.system_prompt else self.prompt\n tlm = TLM(\n api_key=self.api_key,\n options={\"log\": [\"explanation\"], \"model\": self.model},\n quality_preset=self.quality_preset,\n )\n self._cached_result = tlm.get_trustworthiness_score(full_prompt, self.response)\n return self._cached_result\n\n def get_score(self) -> float:\n result = self._evaluate_once()\n score = result.get(\"trustworthiness_score\", 0.0)\n self.status = f\"Trust score: {score:.2f}\"\n return score\n\n def get_explanation(self) -> Message:\n result = self._evaluate_once()\n explanation = result.get(\"log\", {}).get(\"explanation\", \"No explanation returned.\")\n return Message(text=explanation)\n\n def pass_response(self) -> Message:\n self.status = \"Passing through response.\"\n return Message(text=self.response)\n"},"model":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Cleanlab Evaluation Model","dynamic":false,"external_options":{},"info":"The model Cleanlab uses to evaluate the response. This does NOT need to be the same model that generated the response.","name":"model","options":["gpt-4.1","gpt-4.1-mini","gpt-4.1-nano","o4-mini","o3","gpt-4.5-preview","gpt-4o-mini","gpt-4o","o3-mini","o1","o1-mini","gpt-4","gpt-3.5-turbo-16k","claude-3.7-sonnet","claude-3.5-sonnet-v2","claude-3.5-sonnet","claude-3.5-haiku","claude-3-haiku","nova-micro","nova-lite","nova-pro"],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"gpt-4o-mini"},"prompt":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Prompt","dynamic":false,"info":"The user's query to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"prompt","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"quality_preset":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Quality Preset","dynamic":false,"external_options":{},"info":"This determines the accuracy, latency, and cost of the evaluation. Higher quality is generally slower but more accurate.","name":"quality_preset","options":["base","low","medium","high","best"],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"medium"},"response":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Response","dynamic":false,"info":"The response to the user's query.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"response","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"system_prompt":{"_input_type":"MessageTextInput","advanced":false,"display_name":"System Message","dynamic":false,"info":"System-level instructions prepended to the user query.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"system_prompt","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":""}},"tool_mode":false},"CleanlabRAGEvaluator":{"base_classes":["Data","dict","float","Message","number"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Evaluates context, query, and response from a RAG pipeline using Cleanlab and outputs trust metrics.","display_name":"Cleanlab RAG Evaluator","documentation":"","edited":false,"field_order":["api_key","model","quality_preset","context","query","response","run_context_sufficiency","run_response_groundedness","run_response_helpfulness","run_query_ease"],"frozen":false,"icon":"Cleanlab","legacy":false,"metadata":{"code_hash":"f48b57ff7ca3","dependencies":{"dependencies":[{"name":"cleanlab_tlm","version":"1.1.39"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.cleanlab.cleanlab_rag_evaluator.CleanlabRAGEvaluator"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Response","group_outputs":false,"method":"pass_response","name":"response_passthrough","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Trust Score","group_outputs":false,"method":"get_trust_score","name":"trust_score","selected":"number","tool_mode":true,"types":["number","float"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Explanation","group_outputs":false,"method":"get_trust_explanation","name":"trust_explanation","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Other Evals","group_outputs":false,"method":"get_other_scores","name":"other_scores","selected":"Data","tool_mode":true,"types":["Data","dict"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Evaluation Summary","group_outputs":false,"method":"get_evaluation_summary","name":"evaluation_summary","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Cleanlab API Key","dynamic":false,"info":"Your Cleanlab 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":"from cleanlab_tlm import TrustworthyRAG, get_default_evals\n\nfrom lfx.custom import Component\nfrom lfx.io import (\n BoolInput,\n DropdownInput,\n MessageTextInput,\n Output,\n SecretStrInput,\n)\nfrom lfx.schema.message import Message\n\n\nclass CleanlabRAGEvaluator(Component):\n \"\"\"A component that evaluates the quality of RAG (Retrieval-Augmented Generation) outputs using Cleanlab.\n\n This component takes a query, retrieved context, and generated response from a RAG pipeline,\n and uses Cleanlab's evaluation algorithms to assess various aspects of the RAG system's performance.\n\n The component can evaluate:\n - Overall trustworthiness of the LLM generated response\n - Context sufficiency (whether the retrieved context contains information needed to answer the query)\n - Response groundedness (whether the response is supported directly by the context)\n - Response helpfulness (whether the response effectively addresses the user's query)\n - Query ease (whether the user query seems easy for an AI system to properly handle, useful to diagnose\n queries that are: complex, vague, tricky, or disgruntled-sounding)\n\n Outputs:\n - Trust Score: A score between 0-1 corresponding to the trustworthiness of the response. A higher score\n indicates a higher confidence that the response is correct/good.\n - Explanation: An LLM generated explanation of the trustworthiness assessment\n - Other Evals: Additional evaluation metrics for selected evaluation types in the \"Controls\" tab\n - Evaluation Summary: A comprehensive summary of context, query, response, and selected evaluation results\n\n This component works well in conjunction with the CleanlabRemediator to create a complete trust evaluation\n and remediation pipeline.\n\n More details on the evaluation metrics can be found here: https://help.cleanlab.ai/tlm/use-cases/tlm_rag/\n \"\"\"\n\n display_name = \"Cleanlab RAG Evaluator\"\n description = \"Evaluates context, query, and response from a RAG pipeline using Cleanlab and outputs trust metrics.\"\n icon = \"Cleanlab\"\n name = \"CleanlabRAGEvaluator\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Cleanlab API Key\",\n info=\"Your Cleanlab API key.\",\n required=True,\n ),\n DropdownInput(\n name=\"model\",\n display_name=\"Cleanlab Evaluation Model\",\n options=[\n \"gpt-4.1\",\n \"gpt-4.1-mini\",\n \"gpt-4.1-nano\",\n \"o4-mini\",\n \"o3\",\n \"gpt-4.5-preview\",\n \"gpt-4o-mini\",\n \"gpt-4o\",\n \"o3-mini\",\n \"o1\",\n \"o1-mini\",\n \"gpt-4\",\n \"gpt-3.5-turbo-16k\",\n \"claude-3.7-sonnet\",\n \"claude-3.5-sonnet-v2\",\n \"claude-3.5-sonnet\",\n \"claude-3.5-haiku\",\n \"claude-3-haiku\",\n \"nova-micro\",\n \"nova-lite\",\n \"nova-pro\",\n ],\n info=\"The model Cleanlab uses to evaluate the context, query, and response. This does NOT need to be \"\n \"the same model that generated the response.\",\n value=\"gpt-4o-mini\",\n required=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"quality_preset\",\n display_name=\"Quality Preset\",\n options=[\"base\", \"low\", \"medium\"],\n value=\"medium\",\n info=\"This determines the accuracy, latency, and cost of the evaluation. Higher quality is generally \"\n \"slower but more accurate.\",\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"context\",\n display_name=\"Context\",\n info=\"The context retrieved for the given query.\",\n required=True,\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Query\",\n info=\"The user's query.\",\n required=True,\n ),\n MessageTextInput(\n name=\"response\",\n display_name=\"Response\",\n info=\"The response generated by the LLM.\",\n required=True,\n ),\n BoolInput(\n name=\"run_context_sufficiency\",\n display_name=\"Run Context Sufficiency\",\n value=False,\n advanced=True,\n ),\n BoolInput(\n name=\"run_response_groundedness\",\n display_name=\"Run Response Groundedness\",\n value=False,\n advanced=True,\n ),\n BoolInput(\n name=\"run_response_helpfulness\",\n display_name=\"Run Response Helpfulness\",\n value=False,\n advanced=True,\n ),\n BoolInput(\n name=\"run_query_ease\",\n display_name=\"Run Query Ease\",\n value=False,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Response\", name=\"response_passthrough\", method=\"pass_response\", types=[\"Message\"]),\n Output(display_name=\"Trust Score\", name=\"trust_score\", method=\"get_trust_score\", types=[\"number\"]),\n Output(display_name=\"Explanation\", name=\"trust_explanation\", method=\"get_trust_explanation\", types=[\"Message\"]),\n Output(display_name=\"Other Evals\", name=\"other_scores\", method=\"get_other_scores\", types=[\"Data\"]),\n Output(\n display_name=\"Evaluation Summary\",\n name=\"evaluation_summary\",\n method=\"get_evaluation_summary\",\n types=[\"Message\"],\n ),\n ]\n\n def _evaluate_once(self):\n if not hasattr(self, \"_cached_result\"):\n try:\n self.status = \"Configuring selected evals...\"\n default_evals = get_default_evals()\n enabled_names = []\n if self.run_context_sufficiency:\n enabled_names.append(\"context_sufficiency\")\n if self.run_response_groundedness:\n enabled_names.append(\"response_groundedness\")\n if self.run_response_helpfulness:\n enabled_names.append(\"response_helpfulness\")\n if self.run_query_ease:\n enabled_names.append(\"query_ease\")\n\n selected_evals = [e for e in default_evals if e.name in enabled_names]\n\n validator = TrustworthyRAG(\n api_key=self.api_key,\n quality_preset=self.quality_preset,\n options={\"log\": [\"explanation\"], \"model\": self.model},\n evals=selected_evals,\n )\n\n self.status = f\"Running evals: {[e.name for e in selected_evals]}\"\n self._cached_result = validator.score(\n query=self.query,\n context=self.context,\n response=self.response,\n )\n self.status = \"Evaluation complete.\"\n\n except Exception as e: # noqa: BLE001\n self.status = f\"Evaluation failed: {e!s}\"\n self._cached_result = {}\n return self._cached_result\n\n def pass_response(self) -> Message:\n self.status = \"Passing through response.\"\n return Message(text=self.response)\n\n def get_trust_score(self) -> float:\n score = self._evaluate_once().get(\"trustworthiness\", {}).get(\"score\", 0.0)\n self.status = f\"Trust Score: {score:.3f}\"\n return score\n\n def get_trust_explanation(self) -> Message:\n explanation = self._evaluate_once().get(\"trustworthiness\", {}).get(\"log\", {}).get(\"explanation\", \"\")\n self.status = \"Trust explanation extracted.\"\n return Message(text=explanation)\n\n def get_other_scores(self) -> dict:\n result = self._evaluate_once()\n\n selected = {\n \"context_sufficiency\": self.run_context_sufficiency,\n \"response_groundedness\": self.run_response_groundedness,\n \"response_helpfulness\": self.run_response_helpfulness,\n \"query_ease\": self.run_query_ease,\n }\n\n filtered_scores = {key: result[key][\"score\"] for key, include in selected.items() if include and key in result}\n\n self.status = f\"{len(filtered_scores)} other evals returned.\"\n return filtered_scores\n\n def get_evaluation_summary(self) -> Message:\n result = self._evaluate_once()\n\n query_text = self.query.strip()\n context_text = self.context.strip()\n response_text = self.response.strip()\n\n trust = result.get(\"trustworthiness\", {}).get(\"score\", 0.0)\n trust_exp = result.get(\"trustworthiness\", {}).get(\"log\", {}).get(\"explanation\", \"\")\n\n selected = {\n \"context_sufficiency\": self.run_context_sufficiency,\n \"response_groundedness\": self.run_response_groundedness,\n \"response_helpfulness\": self.run_response_helpfulness,\n \"query_ease\": self.run_query_ease,\n }\n\n other_scores = {key: result[key][\"score\"] for key, include in selected.items() if include and key in result}\n\n metrics = f\"Trustworthiness: {trust:.3f}\"\n if trust_exp:\n metrics += f\"\\nExplanation: {trust_exp}\"\n if other_scores:\n metrics += \"\\n\" + \"\\n\".join(f\"{k.replace('_', ' ').title()}: {v:.3f}\" for k, v in other_scores.items())\n\n summary = (\n f\"Query:\\n{query_text}\\n\"\n \"-----\\n\"\n f\"Context:\\n{context_text}\\n\"\n \"-----\\n\"\n f\"Response:\\n{response_text}\\n\"\n \"------------------------------\\n\"\n f\"{metrics}\"\n )\n\n self.status = \"Evaluation summary built.\"\n return Message(text=summary)\n"},"context":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Context","dynamic":false,"info":"The context retrieved for the given query.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"context","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"model":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Cleanlab Evaluation Model","dynamic":false,"external_options":{},"info":"The model Cleanlab uses to evaluate the context, query, and response. This does NOT need to be the same model that generated the response.","name":"model","options":["gpt-4.1","gpt-4.1-mini","gpt-4.1-nano","o4-mini","o3","gpt-4.5-preview","gpt-4o-mini","gpt-4o","o3-mini","o1","o1-mini","gpt-4","gpt-3.5-turbo-16k","claude-3.7-sonnet","claude-3.5-sonnet-v2","claude-3.5-sonnet","claude-3.5-haiku","claude-3-haiku","nova-micro","nova-lite","nova-pro"],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"gpt-4o-mini"},"quality_preset":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Quality Preset","dynamic":false,"external_options":{},"info":"This determines the accuracy, latency, and cost of the evaluation. Higher quality is generally slower but more accurate.","name":"quality_preset","options":["base","low","medium"],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"medium"},"query":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Query","dynamic":false,"info":"The user's query.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"query","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"response":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Response","dynamic":false,"info":"The response generated by the LLM.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"response","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"run_context_sufficiency":{"_input_type":"BoolInput","advanced":true,"display_name":"Run Context Sufficiency","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"run_context_sufficiency","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"run_query_ease":{"_input_type":"BoolInput","advanced":true,"display_name":"Run Query Ease","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"run_query_ease","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"run_response_groundedness":{"_input_type":"BoolInput","advanced":true,"display_name":"Run Response Groundedness","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"run_response_groundedness","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"run_response_helpfulness":{"_input_type":"BoolInput","advanced":true,"display_name":"Run Response Helpfulness","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"run_response_helpfulness","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"CleanlabRemediator":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Remediates an untrustworthy response based on trust score from the Cleanlab Evaluator, score threshold, and message handling settings.","display_name":"Cleanlab Remediator","documentation":"","edited":false,"field_order":["response","score","explanation","threshold","show_untrustworthy_response","untrustworthy_warning_text","fallback_text"],"frozen":false,"icon":"Cleanlab","legacy":false,"metadata":{"code_hash":"a5b19d338991","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.cleanlab.cleanlab_remediator.CleanlabRemediator"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Remediated Message","group_outputs":false,"method":"remediate_response","name":"remediated_response","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from lfx.custom import Component\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import BoolInput, FloatInput, HandleInput, MessageTextInput, Output, PromptInput\nfrom lfx.schema.message import Message\n\n\nclass CleanlabRemediator(Component):\n \"\"\"Remediates potentially untrustworthy LLM responses based on trust scores computed by the Cleanlab Evaluator.\n\n This component takes a response and its associated trust score,\n and applies remediation strategies based on configurable thresholds and settings.\n\n Inputs:\n - response (MessageTextInput): The original LLM-generated response to be evaluated and possibly remediated.\n The CleanlabEvaluator passes this response through.\n - score (HandleInput): The trust score output from CleanlabEvaluator (expected to be a float between 0 and 1).\n - explanation (MessageTextInput): Optional textual explanation for the trust score, to be included in the\n output.\n - threshold (Input[float]): Minimum trust score required to accept the response. If the score is lower, the\n response is remediated.\n - show_untrustworthy_response (BoolInput): If true, returns the original response with a warning; if false,\n returns fallback text.\n - untrustworthy_warning_text (PromptInput): Text warning to append to responses deemed untrustworthy (when\n showing them).\n - fallback_text (PromptInput): Replacement message returned if the response is untrustworthy and should be\n hidden.\n\n Outputs:\n - remediated_response (Message): Either:\n • the original response,\n • the original response with appended warning, or\n • the fallback response,\n depending on the trust score and configuration.\n\n This component is typically used downstream of CleanlabEvaluator or CleanlabRagValidator\n to take appropriate action on low-trust responses and inform users accordingly.\n \"\"\"\n\n display_name = \"Cleanlab Remediator\"\n description = (\n \"Remediates an untrustworthy response based on trust score from the Cleanlab Evaluator, \"\n \"score threshold, and message handling settings.\"\n )\n icon = \"Cleanlab\"\n name = \"CleanlabRemediator\"\n\n inputs = [\n MessageTextInput(\n name=\"response\",\n display_name=\"Response\",\n info=\"The response to the user's query.\",\n required=True,\n ),\n HandleInput(\n name=\"score\",\n display_name=\"Trust Score\",\n info=\"The trustworthiness score output from the Cleanlab Evaluator.\",\n input_types=[\"number\"],\n required=True,\n ),\n MessageTextInput(\n name=\"explanation\",\n display_name=\"Explanation\",\n info=\"The explanation from the Cleanlab Evaluator.\",\n required=False,\n ),\n FloatInput(\n name=\"threshold\",\n display_name=\"Threshold\",\n field_type=\"float\",\n value=0.7,\n range_spec=RangeSpec(min=0.0, max=1.0, step=0.05),\n info=\"Minimum score required to show the response unmodified. Reponses with scores above this threshold \"\n \"are considered trustworthy. Reponses with scores below this threshold are considered untrustworthy and \"\n \"will be remediated based on the settings below.\",\n required=True,\n show=True,\n ),\n BoolInput(\n name=\"show_untrustworthy_response\",\n display_name=\"Show Untrustworthy Response\",\n info=\"If enabled, and the trust score is below the threshold, the original response is shown with the \"\n \"added warning. If disabled, and the trust score is below the threshold, the fallback answer is returned.\",\n value=True,\n ),\n PromptInput(\n name=\"untrustworthy_warning_text\",\n display_name=\"Warning for Untrustworthy Response\",\n info=\"Warning to append to the response if Show Untrustworthy Response is enabled and trust score is \"\n \"below the threshold.\",\n value=\"⚠️ WARNING: The following response is potentially untrustworthy.\",\n ),\n PromptInput(\n name=\"fallback_text\",\n display_name=\"Fallback Answer\",\n info=\"Response returned if the trust score is below the threshold and 'Show Untrustworthy Response' is \"\n \"disabled.\",\n value=\"Based on the available information, I cannot provide a complete answer to this question.\",\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Remediated Message\",\n name=\"remediated_response\",\n method=\"remediate_response\",\n types=[\"Message\"],\n ),\n ]\n\n def remediate_response(self) -> Message:\n if self.score >= self.threshold:\n self.status = f\"Score {self.score:.2f} ≥ threshold {self.threshold:.2f} → accepted\"\n return Message(\n text=f\"{self.response}\\n\\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n\\n**Trust Score:** {self.score:.2f}\"\n )\n\n self.status = f\"Score {self.score:.2f} < threshold {self.threshold:.2f} → flagged\"\n\n if self.show_untrustworthy_response:\n parts = [\n self.response,\n \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n f\"**{self.untrustworthy_warning_text.strip()}**\",\n f\"**Trust Score:** {self.score:.2f}\",\n ]\n if self.explanation:\n parts.append(f\"**Explanation:** {self.explanation}\")\n return Message(text=\"\\n\\n\".join(parts))\n\n return Message(text=self.fallback_text)\n"},"explanation":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Explanation","dynamic":false,"info":"The explanation from the Cleanlab Evaluator.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"explanation","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":""},"fallback_text":{"_input_type":"PromptInput","advanced":false,"display_name":"Fallback Answer","dynamic":false,"info":"Response returned if the trust score is below the threshold and 'Show Untrustworthy Response' is disabled.","list":false,"list_add_label":"Add More","name":"fallback_text","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"prompt","value":"Based on the available information, I cannot provide a complete answer to this question."},"response":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Response","dynamic":false,"info":"The response to the user's query.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"response","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"score":{"_input_type":"HandleInput","advanced":false,"display_name":"Trust Score","dynamic":false,"info":"The trustworthiness score output from the Cleanlab Evaluator.","input_types":["number"],"list":false,"list_add_label":"Add More","name":"score","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"show_untrustworthy_response":{"_input_type":"BoolInput","advanced":false,"display_name":"Show Untrustworthy Response","dynamic":false,"info":"If enabled, and the trust score is below the threshold, the original response is shown with the added warning. If disabled, and the trust score is below the threshold, the fallback answer is returned.","list":false,"list_add_label":"Add More","name":"show_untrustworthy_response","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"threshold":{"_input_type":"FloatInput","advanced":false,"display_name":"Threshold","dynamic":false,"info":"Minimum score required to show the response unmodified. Reponses with scores above this threshold are considered trustworthy. Reponses with scores below this threshold are considered untrustworthy and will be remediated based on the settings below.","list":false,"list_add_label":"Add More","name":"threshold","override_skip":false,"placeholder":"","range_spec":{"max":1.0,"min":0.0,"step":0.05,"step_type":"float"},"required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.7},"untrustworthy_warning_text":{"_input_type":"PromptInput","advanced":false,"display_name":"Warning for Untrustworthy Response","dynamic":false,"info":"Warning to append to the response if Show Untrustworthy Response is enabled and trust score is below the threshold.","list":false,"list_add_label":"Add More","name":"untrustworthy_warning_text","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"prompt","value":"⚠️ WARNING: The following response is potentially untrustworthy."}},"tool_mode":false}}],["clickhouse",{"Clickhouse":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"ClickHouse Vector Store with search capabilities","display_name":"ClickHouse","documentation":"","edited":false,"field_order":["host","port","database","table","username","password","index_type","metric","secure","index_param","index_query_params","ingest_data","search_query","should_cache_vector_store","embedding","number_of_results","score_threshold"],"frozen":false,"icon":"Clickhouse","legacy":false,"metadata":{"code_hash":"ab991e83da44","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null},{"name":"clickhouse_connect","version":"0.7.19"}],"total_dependencies":3},"module":"lfx.components.clickhouse.clickhouse.ClickhouseVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from langchain_community.vectorstores import Clickhouse, ClickhouseSettings\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.inputs.inputs import BoolInput, FloatInput\nfrom lfx.io import (\n DictInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom lfx.schema.data import Data\n\n\nclass ClickhouseVectorStoreComponent(LCVectorStoreComponent):\n display_name = \"ClickHouse\"\n description = \"ClickHouse Vector Store with search capabilities\"\n name = \"Clickhouse\"\n icon = \"Clickhouse\"\n\n inputs = [\n StrInput(name=\"host\", display_name=\"hostname\", required=True, value=\"localhost\"),\n IntInput(name=\"port\", display_name=\"port\", required=True, value=8123),\n StrInput(name=\"database\", display_name=\"database\", required=True),\n StrInput(name=\"table\", display_name=\"Table name\", required=True),\n StrInput(name=\"username\", display_name=\"The ClickHouse user name.\", required=True),\n SecretStrInput(name=\"password\", display_name=\"Clickhouse Password\", required=True),\n DropdownInput(\n name=\"index_type\",\n display_name=\"index_type\",\n options=[\"annoy\", \"vector_similarity\"],\n info=\"Type of the index.\",\n value=\"annoy\",\n advanced=True,\n ),\n DropdownInput(\n name=\"metric\",\n display_name=\"metric\",\n options=[\"angular\", \"euclidean\", \"manhattan\", \"hamming\", \"dot\"],\n info=\"Metric to compute distance.\",\n value=\"angular\",\n advanced=True,\n ),\n BoolInput(\n name=\"secure\",\n display_name=\"Use https/TLS. This overrides inferred values from the interface or port arguments.\",\n value=False,\n advanced=True,\n ),\n StrInput(name=\"index_param\", display_name=\"Param of the index\", value=\"100,'L2Distance'\", advanced=True),\n DictInput(name=\"index_query_params\", display_name=\"index query params\", advanced=True),\n *LCVectorStoreComponent.inputs,\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n value=4,\n advanced=True,\n ),\n FloatInput(name=\"score_threshold\", display_name=\"Score threshold\", advanced=True),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self) -> Clickhouse:\n try:\n import clickhouse_connect\n except ImportError as e:\n msg = (\n \"Failed to import ClickHouse dependencies. \"\n \"Install it using `uv pip install langflow[clickhouse-connect] --pre`\"\n )\n raise ImportError(msg) from e\n\n try:\n client = clickhouse_connect.get_client(\n host=self.host, port=self.port, username=self.username, password=self.password\n )\n client.command(\"SELECT 1\")\n except Exception as e:\n msg = f\"Failed to connect to Clickhouse: {e}\"\n raise ValueError(msg) from e\n\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n kwargs = {}\n if self.index_param:\n kwargs[\"index_param\"] = self.index_param.split(\",\")\n if self.index_query_params:\n kwargs[\"index_query_params\"] = self.index_query_params\n\n settings = ClickhouseSettings(\n table=self.table,\n database=self.database,\n host=self.host,\n index_type=self.index_type,\n metric=self.metric,\n password=self.password,\n port=self.port,\n secure=self.secure,\n username=self.username,\n **kwargs,\n )\n if documents:\n clickhouse_vs = Clickhouse.from_documents(documents=documents, embedding=self.embedding, config=settings)\n\n else:\n clickhouse_vs = Clickhouse(embedding=self.embedding, config=settings)\n\n return clickhouse_vs\n\n def search_documents(self) -> list[Data]:\n vector_store = self.build_vector_store()\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n kwargs = {}\n if self.score_threshold:\n kwargs[\"score_threshold\"] = self.score_threshold\n\n docs = vector_store.similarity_search(query=self.search_query, k=self.number_of_results, **kwargs)\n\n data = docs_to_data(docs)\n self.status = data\n return data\n return []\n"},"database":{"_input_type":"StrInput","advanced":false,"display_name":"database","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"database","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"host":{"_input_type":"StrInput","advanced":false,"display_name":"hostname","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"host","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"localhost"},"index_param":{"_input_type":"StrInput","advanced":true,"display_name":"Param of the index","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"index_param","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"100,'L2Distance'"},"index_query_params":{"_input_type":"DictInput","advanced":true,"display_name":"index query params","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"index_query_params","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"index_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"index_type","dynamic":false,"external_options":{},"info":"Type of the index.","name":"index_type","options":["annoy","vector_similarity"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"annoy"},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"metric":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"metric","dynamic":false,"external_options":{},"info":"Metric to compute distance.","name":"metric","options":["angular","euclidean","manhattan","hamming","dot"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"angular"},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Clickhouse Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"port":{"_input_type":"IntInput","advanced":false,"display_name":"port","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"port","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int","value":8123},"score_threshold":{"_input_type":"FloatInput","advanced":true,"display_name":"Score threshold","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"score_threshold","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":""},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"secure":{"_input_type":"BoolInput","advanced":true,"display_name":"Use https/TLS. This overrides inferred values from the interface or port arguments.","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"secure","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"table":{"_input_type":"StrInput","advanced":false,"display_name":"Table name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"table","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"The ClickHouse user name.","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false}}],["cloudflare",{"CloudflareWorkersAIEmbeddings":{"base_classes":["Embeddings"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate embeddings using Cloudflare Workers AI models.","display_name":"Cloudflare Workers AI Embeddings","documentation":"https://python.langchain.com/docs/integrations/text_embedding/cloudflare_workersai/","edited":false,"field_order":["account_id","api_token","model_name","strip_new_lines","batch_size","api_base_url","headers"],"frozen":false,"icon":"Cloudflare","legacy":false,"metadata":{"code_hash":"1ea6e4857c14","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null}],"total_dependencies":2},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.cloudflare.cloudflare.CloudflareWorkersAIEmbeddingsComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Embeddings","group_outputs":false,"method":"build_embeddings","name":"embeddings","selected":"Embeddings","tool_mode":true,"types":["Embeddings"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","account_id":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Cloudflare account ID","dynamic":false,"info":"Find your account ID https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/#find-account-id-workers-and-pages","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"account_id","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"api_base_url":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Cloudflare API base URL","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"api_base_url","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":"https://api.cloudflare.com/client/v4/accounts"},"api_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Cloudflare API token","dynamic":false,"info":"Create an API token https://developers.cloudflare.com/fundamentals/api/get-started/create-token/","input_types":[],"load_from_db":true,"name":"api_token","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"batch_size":{"_input_type":"IntInput","advanced":true,"display_name":"Batch Size","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"batch_size","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":50},"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":"from langchain_community.embeddings.cloudflare_workersai import CloudflareWorkersAIEmbeddings\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import BoolInput, DictInput, IntInput, MessageTextInput, Output, SecretStrInput\n\n\nclass CloudflareWorkersAIEmbeddingsComponent(LCModelComponent):\n display_name: str = \"Cloudflare Workers AI Embeddings\"\n description: str = \"Generate embeddings using Cloudflare Workers AI models.\"\n documentation: str = \"https://python.langchain.com/docs/integrations/text_embedding/cloudflare_workersai/\"\n icon = \"Cloudflare\"\n name = \"CloudflareWorkersAIEmbeddings\"\n\n inputs = [\n MessageTextInput(\n name=\"account_id\",\n display_name=\"Cloudflare account ID\",\n info=\"Find your account ID https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/#find-account-id-workers-and-pages\",\n required=True,\n ),\n SecretStrInput(\n name=\"api_token\",\n display_name=\"Cloudflare API token\",\n info=\"Create an API token https://developers.cloudflare.com/fundamentals/api/get-started/create-token/\",\n required=True,\n ),\n MessageTextInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n info=\"List of supported models https://developers.cloudflare.com/workers-ai/models/#text-embeddings\",\n required=True,\n value=\"@cf/baai/bge-base-en-v1.5\",\n ),\n BoolInput(\n name=\"strip_new_lines\",\n display_name=\"Strip New Lines\",\n advanced=True,\n value=True,\n ),\n IntInput(\n name=\"batch_size\",\n display_name=\"Batch Size\",\n advanced=True,\n value=50,\n ),\n MessageTextInput(\n name=\"api_base_url\",\n display_name=\"Cloudflare API base URL\",\n advanced=True,\n value=\"https://api.cloudflare.com/client/v4/accounts\",\n ),\n DictInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"Additional request headers\",\n is_list=True,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n try:\n embeddings = CloudflareWorkersAIEmbeddings(\n account_id=self.account_id,\n api_base_url=self.api_base_url,\n api_token=self.api_token,\n batch_size=self.batch_size,\n headers=self.headers,\n model_name=self.model_name,\n strip_new_lines=self.strip_new_lines,\n )\n except Exception as e:\n msg = f\"Could not connect to CloudflareWorkersAIEmbeddings API: {e!s}\"\n raise ValueError(msg) from e\n\n return embeddings\n"},"headers":{"_input_type":"DictInput","advanced":true,"display_name":"Headers","dynamic":false,"info":"Additional request headers","list":true,"list_add_label":"Add More","name":"headers","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"model_name":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Model Name","dynamic":false,"info":"List of supported models https://developers.cloudflare.com/workers-ai/models/#text-embeddings","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"model_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"@cf/baai/bge-base-en-v1.5"},"strip_new_lines":{"_input_type":"BoolInput","advanced":true,"display_name":"Strip New Lines","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"strip_new_lines","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false}}],["cohere",{"CohereEmbeddings":{"base_classes":["Embeddings"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate embeddings using Cohere models.","display_name":"Cohere Embeddings","documentation":"","edited":false,"field_order":["api_key","model_name","truncate","max_retries","user_agent","request_timeout"],"frozen":false,"icon":"Cohere","legacy":false,"metadata":{"code_hash":"9c0f413a2c64","dependencies":{"dependencies":[{"name":"cohere","version":"5.6.2"},{"name":"langchain_cohere","version":"0.3.5"},{"name":"lfx","version":null}],"total_dependencies":3},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.cohere.cohere_embeddings.CohereEmbeddingsComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Embeddings","group_outputs":false,"method":"build_embeddings","name":"embeddings","selected":"Embeddings","tool_mode":true,"types":["Embeddings"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Cohere API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"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":"from typing import Any\n\nimport cohere\nfrom langchain_cohere import CohereEmbeddings\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import DropdownInput, FloatInput, IntInput, MessageTextInput, Output, SecretStrInput\n\nHTTP_STATUS_OK = 200\n\n\nclass CohereEmbeddingsComponent(LCModelComponent):\n display_name = \"Cohere Embeddings\"\n description = \"Generate embeddings using Cohere models.\"\n icon = \"Cohere\"\n name = \"CohereEmbeddings\"\n\n inputs = [\n SecretStrInput(name=\"api_key\", display_name=\"Cohere API Key\", required=True, real_time_refresh=True),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model\",\n advanced=False,\n options=[\n \"embed-english-v2.0\",\n \"embed-multilingual-v2.0\",\n \"embed-english-light-v2.0\",\n \"embed-multilingual-light-v2.0\",\n ],\n value=\"embed-english-v2.0\",\n refresh_button=True,\n combobox=True,\n ),\n MessageTextInput(name=\"truncate\", display_name=\"Truncate\", advanced=True),\n IntInput(name=\"max_retries\", display_name=\"Max Retries\", value=3, advanced=True),\n MessageTextInput(name=\"user_agent\", display_name=\"User Agent\", advanced=True, value=\"langchain\"),\n FloatInput(name=\"request_timeout\", display_name=\"Request Timeout\", advanced=True),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n data = None\n try:\n data = CohereEmbeddings(\n cohere_api_key=self.api_key,\n model=self.model_name,\n truncate=self.truncate,\n max_retries=self.max_retries,\n user_agent=self.user_agent,\n request_timeout=self.request_timeout or None,\n )\n except Exception as e:\n msg = (\n \"Unable to create Cohere Embeddings. \",\n \"Please verify the API key and model parameters, and try again.\",\n )\n raise ValueError(msg) from e\n # added status if not the return data would be serialised to create the status\n return data\n\n def get_model(self):\n try:\n co = cohere.ClientV2(self.api_key)\n response = co.models.list(endpoint=\"embed\")\n models = response.models\n return [model.name for model in models]\n except Exception as e:\n msg = f\"Failed to fetch Cohere models. Error: {e}\"\n raise ValueError(msg) from e\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name in {\"model_name\", \"api_key\"}:\n if build_config.get(\"api_key\", {}).get(\"value\", None):\n build_config[\"model_name\"][\"options\"] = self.get_model()\n else:\n build_config[\"model_name\"][\"options\"] = field_value\n return build_config\n"},"max_retries":{"_input_type":"IntInput","advanced":true,"display_name":"Max Retries","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"max_retries","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":3},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{},"display_name":"Model","dynamic":false,"external_options":{},"info":"","name":"model_name","options":["embed-english-v2.0","embed-multilingual-v2.0","embed-english-light-v2.0","embed-multilingual-light-v2.0"],"options_metadata":[],"override_skip":false,"placeholder":"","refresh_button":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"embed-english-v2.0"},"request_timeout":{"_input_type":"FloatInput","advanced":true,"display_name":"Request Timeout","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"request_timeout","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":""},"truncate":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Truncate","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"truncate","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":""},"user_agent":{"_input_type":"MessageTextInput","advanced":true,"display_name":"User Agent","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"user_agent","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":"langchain"}},"tool_mode":false},"CohereModel":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate text using Cohere LLMs.","display_name":"Cohere Language Models","documentation":"https://python.langchain.com/docs/integrations/llms/cohere/","edited":false,"field_order":["input_value","system_message","stream","cohere_api_key","temperature"],"frozen":false,"icon":"Cohere","legacy":false,"metadata":{"code_hash":"594852e1d706","dependencies":{"dependencies":[{"name":"langchain_cohere","version":"0.3.5"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":3},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.cohere.cohere_models.CohereComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from langchain_cohere import ChatCohere\nfrom pydantic.v1 import SecretStr\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import SecretStrInput, SliderInput\n\n\nclass CohereComponent(LCModelComponent):\n display_name = \"Cohere Language Models\"\n description = \"Generate text using Cohere LLMs.\"\n documentation = \"https://python.langchain.com/docs/integrations/llms/cohere/\"\n icon = \"Cohere\"\n name = \"CohereModel\"\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n SecretStrInput(\n name=\"cohere_api_key\",\n display_name=\"Cohere API Key\",\n info=\"The Cohere API Key to use for the Cohere model.\",\n advanced=False,\n value=\"COHERE_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.75,\n range_spec=RangeSpec(min=0, max=2, step=0.01),\n info=\"Controls randomness. Lower values are more deterministic, higher values are more creative.\",\n advanced=True,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n cohere_api_key = self.cohere_api_key\n temperature = self.temperature\n\n api_key = SecretStr(cohere_api_key).get_secret_value() if cohere_api_key else None\n\n return ChatCohere(\n temperature=temperature or 0.75,\n cohere_api_key=api_key,\n )\n"},"cohere_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Cohere API Key","dynamic":false,"info":"The Cohere API Key to use for the Cohere model.","input_types":[],"load_from_db":true,"name":"cohere_api_key","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COHERE_API_KEY"},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"SliderInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"Controls randomness. Lower values are more deterministic, higher values are more creative.","max_label":"","max_label_icon":"","min_label":"","min_label_icon":"","name":"temperature","override_skip":false,"placeholder":"","range_spec":{"max":2.0,"min":0.0,"step":0.01,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":0.75}},"tool_mode":false},"CohereRerank":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Rerank documents using the Cohere API.","display_name":"Cohere Rerank","documentation":"","edited":false,"field_order":["search_query","search_results","top_n","api_key","model"],"frozen":false,"icon":"Cohere","legacy":false,"metadata":{"code_hash":"a94a0d11eeac","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_cohere","version":"0.3.5"}],"total_dependencies":2},"module":"lfx.components.cohere.cohere_rerank.CohereRerankComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Reranked Documents","group_outputs":false,"method":"compress_documents","name":"reranked_documents","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Cohere API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","required":false,"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":"from lfx.base.compressors.model import LCCompressorComponent\nfrom lfx.field_typing import BaseDocumentCompressor\nfrom lfx.inputs.inputs import SecretStrInput\nfrom lfx.io import DropdownInput\nfrom lfx.template.field.base import Output\n\n\nclass CohereRerankComponent(LCCompressorComponent):\n display_name = \"Cohere Rerank\"\n description = \"Rerank documents using the Cohere API.\"\n name = \"CohereRerank\"\n icon = \"Cohere\"\n\n inputs = [\n *LCCompressorComponent.inputs,\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Cohere API Key\",\n ),\n DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n options=[\n \"rerank-english-v3.0\",\n \"rerank-multilingual-v3.0\",\n \"rerank-english-v2.0\",\n \"rerank-multilingual-v2.0\",\n ],\n value=\"rerank-english-v3.0\",\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Reranked Documents\",\n name=\"reranked_documents\",\n method=\"compress_documents\",\n ),\n ]\n\n def build_compressor(self) -> BaseDocumentCompressor: # type: ignore[type-var]\n try:\n from langchain_cohere import CohereRerank\n except ImportError as e:\n msg = \"Please install langchain-cohere to use the Cohere model.\"\n raise ImportError(msg) from e\n return CohereRerank(\n cohere_api_key=self.api_key,\n model=self.model,\n top_n=self.top_n,\n )\n"},"model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model","dynamic":false,"external_options":{},"info":"","name":"model","options":["rerank-english-v3.0","rerank-multilingual-v3.0","rerank-english-v2.0","rerank-multilingual-v2.0"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"rerank-english-v3.0"},"search_query":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Search Query","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"search_query","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"search_results":{"_input_type":"DataInput","advanced":false,"display_name":"Search Results","dynamic":false,"info":"Search Results from a Vector Store.","input_types":["Data"],"list":true,"list_add_label":"Add More","name":"search_results","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":"other","value":""},"top_n":{"_input_type":"IntInput","advanced":true,"display_name":"Top N","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"top_n","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":3}},"tool_mode":false}}],["cometapi",{"CometAPIModel":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"All AI Models in One API 500+ AI Models","display_name":"CometAPI","documentation":"","edited":false,"field_order":["input_value","system_message","stream","api_key","app_name","model_name","model_kwargs","temperature","max_tokens","seed","json_mode"],"frozen":false,"icon":"CometAPI","legacy":false,"metadata":{"code_hash":"4ec4a8852e9c","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain_openai","version":"0.3.23"},{"name":"pydantic","version":"2.11.10"},{"name":"typing_extensions","version":"4.15.0"},{"name":"lfx","version":null}],"total_dependencies":5},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.cometapi.cometapi.CometAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"CometAPI Key","dynamic":false,"info":"Your CometAPI key","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"app_name":{"_input_type":"StrInput","advanced":true,"display_name":"App Name","dynamic":false,"info":"Your app name for CometAPI rankings","list":false,"list_add_label":"Add More","load_from_db":false,"name":"app_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"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\n\nimport requests\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\nfrom typing_extensions import override\n\nfrom lfx.base.models.cometapi_constants import MODEL_NAMES\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n IntInput,\n SecretStrInput,\n SliderInput,\n StrInput,\n)\n\n\nclass CometAPIComponent(LCModelComponent):\n \"\"\"CometAPI component for language models.\"\"\"\n\n display_name = \"CometAPI\"\n description = \"All AI Models in One API 500+ AI Models\"\n icon = \"CometAPI\"\n name = \"CometAPIModel\"\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"CometAPI Key\",\n required=True,\n info=\"Your CometAPI key\",\n real_time_refresh=True,\n ),\n StrInput(\n name=\"app_name\",\n display_name=\"App Name\",\n info=\"Your app name for CometAPI rankings\",\n advanced=True,\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model\",\n info=\"The model to use for chat completion\",\n options=[\"Select a model\"],\n value=\"Select a model\",\n real_time_refresh=True,\n required=True,\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n info=\"Additional keyword arguments to pass to the model.\",\n advanced=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.7,\n range_spec=RangeSpec(min=0, max=2, step=0.01),\n info=\"Controls randomness. Lower values are more deterministic, higher values are more creative.\",\n advanced=True,\n ),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n info=\"Maximum number of tokens to generate\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"Seed for reproducible outputs.\",\n value=1,\n advanced=True,\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n info=\"If enabled, the model will be asked to return a JSON object.\",\n advanced=True,\n ),\n ]\n\n def get_models(self, token_override: str | None = None) -> list[str]:\n base_url = \"https://api.cometapi.com/v1\"\n url = f\"{base_url}/models\"\n\n headers = {\"Content-Type\": \"application/json\"}\n # Add Bearer Authorization when API key is available\n api_key_source = token_override if token_override else getattr(self, \"api_key\", None)\n if api_key_source:\n token = api_key_source.get_secret_value() if isinstance(api_key_source, SecretStr) else str(api_key_source)\n headers[\"Authorization\"] = f\"Bearer {token}\"\n\n try:\n response = requests.get(url, headers=headers, timeout=10)\n response.raise_for_status()\n # Safely parse JSON; fallback to defaults on failure\n try:\n model_list = response.json()\n except (json.JSONDecodeError, ValueError) as e:\n self.status = f\"Error decoding models response: {e}\"\n return MODEL_NAMES\n return [model[\"id\"] for model in model_list.get(\"data\", [])]\n except requests.RequestException as e:\n self.status = f\"Error fetching models: {e}\"\n return MODEL_NAMES\n\n @override\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n if field_name == \"api_key\":\n models = self.get_models(field_value)\n model_cfg = build_config.get(\"model_name\", {})\n # Preserve placeholder (fallback to existing value or a generic prompt)\n placeholder = model_cfg.get(\"placeholder\", model_cfg.get(\"value\", \"Select a model\"))\n current_value = model_cfg.get(\"value\")\n\n options = list(models) if models else []\n # Ensure current value stays visible even if not present in fetched options\n if current_value and current_value not in options:\n options = [current_value, *options]\n\n model_cfg[\"options\"] = options\n model_cfg[\"placeholder\"] = placeholder\n build_config[\"model_name\"] = model_cfg\n return build_config\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = getattr(self, \"model_kwargs\", {}) or {}\n json_mode = self.json_mode\n seed = self.seed\n # Ensure a valid model was selected\n if not model_name or model_name == \"Select a model\":\n msg = \"Please select a valid CometAPI model.\"\n raise ValueError(msg)\n try:\n # Extract raw API key safely\n _api_key = api_key.get_secret_value() if isinstance(api_key, SecretStr) else api_key\n output = ChatOpenAI(\n model=model_name,\n api_key=_api_key or None,\n max_tokens=max_tokens or None,\n temperature=temperature,\n model_kwargs=model_kwargs,\n streaming=bool(self.stream),\n seed=seed,\n base_url=\"https://api.cometapi.com/v1\",\n )\n except (TypeError, ValueError) as e:\n msg = \"Could not connect to CometAPI.\"\n raise ValueError(msg) from e\n\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n"},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"json_mode":{"_input_type":"BoolInput","advanced":true,"display_name":"JSON Mode","dynamic":false,"info":"If enabled, the model will be asked to return a JSON object.","list":false,"list_add_label":"Add More","name":"json_mode","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Max Tokens","dynamic":false,"info":"Maximum number of tokens to generate","list":false,"list_add_label":"Add More","name":"max_tokens","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":""},"model_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Model Kwargs","dynamic":false,"info":"Additional keyword arguments to pass to the model.","list":false,"list_add_label":"Add More","name":"model_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model","dynamic":false,"external_options":{},"info":"The model to use for chat completion","name":"model_name","options":["Select a model"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Select a model"},"seed":{"_input_type":"IntInput","advanced":true,"display_name":"Seed","dynamic":false,"info":"Seed for reproducible outputs.","list":false,"list_add_label":"Add More","name":"seed","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":1},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"SliderInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"Controls randomness. Lower values are more deterministic, higher values are more creative.","max_label":"","max_label_icon":"","min_label":"","min_label_icon":"","name":"temperature","override_skip":false,"placeholder":"","range_spec":{"max":2.0,"min":0.0,"step":0.01,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":0.7}},"tool_mode":false}}],["composio",{"ComposioAPI":{"base_classes":["Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Use Composio toolset to run actions with your agent","display_name":"Composio Tools","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","tool_name","actions"],"frozen":false,"icon":"Composio","legacy":false,"metadata":{"code_hash":"764255821307","dependencies":{"dependencies":[{"name":"composio","version":"0.9.2"},{"name":"composio_langchain","version":"0.9.2"},{"name":"langchain_core","version":"0.3.80"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.composio.composio_api.ComposioAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Tools","group_outputs":false,"method":"build_tool","name":"tools","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","actions":{"_input_type":"SortableListInput","advanced":false,"display_name":"Actions","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"icon":"OctagonAlert","variant":"destructive"},"info":"The actions to use","limit":1,"name":"actions","options":[],"override_skip":false,"placeholder":"Select action","required":false,"search_category":[],"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":""},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"Refer to https://docs.composio.dev/faq/api_key/api_key","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"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":"# Standard library imports\nfrom collections.abc import Sequence\nfrom typing import Any\n\nfrom composio import Composio\nfrom composio_langchain import LangchainProvider\n\n# Third-party imports\nfrom langchain_core.tools import Tool\n\n# Local imports\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.inputs.inputs import (\n ConnectionInput,\n MessageTextInput,\n SecretStrInput,\n SortableListInput,\n)\nfrom lfx.io import Output\nfrom lfx.utils.validate_cloud import raise_error_if_astra_cloud_disable_component\n\n# TODO: We get the list from the API but we need to filter it\nenabled_tools = [\"confluence\", \"discord\", \"dropbox\", \"github\", \"gmail\", \"linkedin\", \"notion\", \"slack\", \"youtube\"]\n\ndisable_component_in_astra_cloud_msg = (\n \"Composio tools are not supported in Astra cloud environment. \"\n \"Please use local storage mode or cloud-based versions of the tools.\"\n)\n\n\nclass ComposioAPIComponent(LCToolComponent):\n display_name: str = \"Composio Tools\"\n description: str = \"Use Composio toolset to run actions with your agent\"\n name = \"ComposioAPI\"\n icon = \"Composio\"\n documentation: str = \"https://docs.composio.dev\"\n\n inputs = [\n # Basic configuration inputs\n MessageTextInput(name=\"entity_id\", display_name=\"Entity ID\", value=\"default\", advanced=True),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Composio API Key\",\n required=True,\n info=\"Refer to https://docs.composio.dev/faq/api_key/api_key\",\n real_time_refresh=True,\n ),\n ConnectionInput(\n name=\"tool_name\",\n display_name=\"Tool Name\",\n placeholder=\"Select a tool...\",\n button_metadata={\"icon\": \"unplug\", \"variant\": \"destructive\"},\n options=[],\n search_category=[],\n value=\"\",\n connection_link=\"\",\n info=\"The name of the tool to use\",\n real_time_refresh=True,\n ),\n SortableListInput(\n name=\"actions\",\n display_name=\"Actions\",\n placeholder=\"Select action\",\n helper_text=\"Please connect before selecting actions.\",\n helper_text_metadata={\"icon\": \"OctagonAlert\", \"variant\": \"destructive\"},\n options=[],\n value=\"\",\n info=\"The actions to use\",\n limit=1,\n show=False,\n ),\n ]\n\n outputs = [\n Output(name=\"tools\", display_name=\"Tools\", method=\"build_tool\"),\n ]\n\n def validate_tool(self, build_config: dict, field_value: Any, tool_name: str | None = None) -> dict:\n # Get the index of the selected tool in the list of options\n selected_tool_index = next(\n (\n ind\n for ind, tool in enumerate(build_config[\"tool_name\"][\"options\"])\n if tool[\"name\"] == field_value\n or (\"validate\" in field_value and tool[\"name\"] == field_value[\"validate\"])\n ),\n None,\n )\n\n # Set the link to be the text 'validated'\n build_config[\"tool_name\"][\"options\"][selected_tool_index][\"link\"] = \"validated\"\n\n # Set the helper text and helper text metadata field of the actions now\n build_config[\"actions\"][\"helper_text\"] = \"\"\n build_config[\"actions\"][\"helper_text_metadata\"] = {\"icon\": \"Check\", \"variant\": \"success\"}\n\n try:\n composio = self._build_wrapper()\n current_tool = tool_name or getattr(self, \"tool_name\", None)\n if not current_tool:\n self.log(\"No tool name available for validate_tool\")\n return build_config\n\n toolkit_slug = current_tool.lower()\n\n tools = composio.tools.get(user_id=self.entity_id, toolkits=[toolkit_slug])\n\n authenticated_actions = []\n for tool in tools:\n if hasattr(tool, \"name\"):\n action_name = tool.name\n display_name = action_name.replace(\"_\", \" \").title()\n authenticated_actions.append({\"name\": action_name, \"display_name\": display_name})\n except (ValueError, ConnectionError, AttributeError) as e:\n self.log(f\"Error getting actions for {current_tool or 'unknown tool'}: {e}\")\n authenticated_actions = []\n\n build_config[\"actions\"][\"options\"] = [\n {\n \"name\": action[\"name\"],\n }\n for action in authenticated_actions\n ]\n\n build_config[\"actions\"][\"show\"] = True\n return build_config\n\n def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:\n if field_name == \"api_key\" or (self.api_key and not build_config[\"tool_name\"][\"options\"]):\n if field_name == \"api_key\" and not field_value:\n build_config[\"tool_name\"][\"options\"] = []\n build_config[\"tool_name\"][\"value\"] = \"\"\n\n # Reset the list of actions\n build_config[\"actions\"][\"show\"] = False\n build_config[\"actions\"][\"options\"] = []\n build_config[\"actions\"][\"value\"] = \"\"\n\n return build_config\n\n # Build the list of available tools\n build_config[\"tool_name\"][\"options\"] = [\n {\n \"name\": app.title(),\n \"icon\": app,\n \"link\": (\n build_config[\"tool_name\"][\"options\"][ind][\"link\"]\n if build_config[\"tool_name\"][\"options\"]\n else \"\"\n ),\n }\n for ind, app in enumerate(enabled_tools)\n ]\n\n return build_config\n\n if field_name == \"tool_name\" and field_value:\n composio = self._build_wrapper()\n\n current_tool_name = (\n field_value\n if isinstance(field_value, str)\n else field_value.get(\"validate\")\n if isinstance(field_value, dict) and \"validate\" in field_value\n else getattr(self, \"tool_name\", None)\n )\n\n if not current_tool_name:\n self.log(\"No tool name available for connection check\")\n return build_config\n\n try:\n toolkit_slug = current_tool_name.lower()\n\n connection_list = composio.connected_accounts.list(\n user_ids=[self.entity_id], toolkit_slugs=[toolkit_slug]\n )\n\n # Check for active connections\n has_active_connections = False\n if (\n connection_list\n and hasattr(connection_list, \"items\")\n and connection_list.items\n and isinstance(connection_list.items, list)\n and len(connection_list.items) > 0\n ):\n for connection in connection_list.items:\n if getattr(connection, \"status\", None) == \"ACTIVE\":\n has_active_connections = True\n break\n\n # Get the index of the selected tool in the list of options\n selected_tool_index = next(\n (\n ind\n for ind, tool in enumerate(build_config[\"tool_name\"][\"options\"])\n if tool[\"name\"] == current_tool_name.title()\n ),\n None,\n )\n\n if has_active_connections:\n # User has active connection\n if selected_tool_index is not None:\n build_config[\"tool_name\"][\"options\"][selected_tool_index][\"link\"] = \"validated\"\n\n # If it's a validation request, validate the tool\n if (isinstance(field_value, dict) and \"validate\" in field_value) or isinstance(field_value, str):\n return self.validate_tool(build_config, field_value, current_tool_name)\n else:\n # No active connection - create OAuth connection\n try:\n connection = composio.toolkits.authorize(user_id=self.entity_id, toolkit=toolkit_slug)\n redirect_url = getattr(connection, \"redirect_url\", None)\n\n if redirect_url and redirect_url.startswith((\"http://\", \"https://\")):\n if selected_tool_index is not None:\n build_config[\"tool_name\"][\"options\"][selected_tool_index][\"link\"] = redirect_url\n elif selected_tool_index is not None:\n build_config[\"tool_name\"][\"options\"][selected_tool_index][\"link\"] = \"error\"\n except (ValueError, ConnectionError, AttributeError) as e:\n self.log(f\"Error creating OAuth connection: {e}\")\n if selected_tool_index is not None:\n build_config[\"tool_name\"][\"options\"][selected_tool_index][\"link\"] = \"error\"\n\n except (ValueError, ConnectionError, AttributeError) as e:\n self.log(f\"Error checking connection status: {e}\")\n\n return build_config\n\n def build_tool(self) -> Sequence[Tool]:\n \"\"\"Build Composio tools based on selected actions.\n\n Returns:\n Sequence[Tool]: List of configured Composio tools.\n \"\"\"\n # Check if we're in Astra cloud environment and raise an error if we are.\n raise_error_if_astra_cloud_disable_component(disable_component_in_astra_cloud_msg)\n composio = self._build_wrapper()\n action_names = [action[\"name\"] for action in self.actions]\n\n # Get toolkits from action names\n toolkits = set()\n for action_name in action_names:\n if \"_\" in action_name:\n toolkit = action_name.split(\"_\")[0].lower()\n toolkits.add(toolkit)\n\n if not toolkits:\n return []\n\n # Get all tools for the relevant toolkits\n all_tools = composio.tools.get(user_id=self.entity_id, toolkits=list(toolkits))\n\n # Filter to only the specific actions we want using list comprehension\n return [tool for tool in all_tools if hasattr(tool, \"name\") and tool.name in action_names]\n\n def _build_wrapper(self) -> Composio:\n \"\"\"Build the Composio wrapper using new SDK.\n\n Returns:\n Composio: The initialized Composio client.\n\n Raises:\n ValueError: If the API key is not found or invalid.\n \"\"\"\n # Check if we're in Astra cloud environment and raise an error if we are.\n raise_error_if_astra_cloud_disable_component(disable_component_in_astra_cloud_msg)\n try:\n if not self.api_key:\n msg = \"Composio API Key is required\"\n raise ValueError(msg)\n return Composio(api_key=self.api_key, provider=LangchainProvider())\n except ValueError as e:\n self.log(f\"Error building Composio wrapper: {e}\")\n msg = \"Please provide a valid Composio API Key in the component settings\"\n raise ValueError(msg) from e\n"},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","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":"default"},"tool_name":{"_input_type":"ConnectionInput","advanced":false,"button_metadata":{"icon":"unplug","variant":"destructive"},"connection_link":"","display_name":"Tool Name","dynamic":false,"info":"The name of the tool to use","name":"tool_name","options":[],"override_skip":false,"placeholder":"Select a tool...","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"connect","value":""}},"tool_mode":false},"ComposioAgentQLAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"AgentQL","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"AgentQL","legacy":false,"metadata":{"code_hash":"cca708a10ab6","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.agentql_composio.ComposioAgentQLAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioAgentQLAPIComponent(ComposioBaseComponent):\n display_name: str = \"AgentQL\"\n icon = \"AgentQL\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"agentql\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for AgentQL component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioAgiledAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Agiled","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Agiled","legacy":false,"metadata":{"code_hash":"3294a951a1a8","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.agiled_composio.ComposioAgiledAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioAgiledAPIComponent(ComposioBaseComponent):\n display_name: str = \"Agiled\"\n icon = \"Agiled\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"agiled\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Agiled component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioAirtableAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Airtable","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Airtable","legacy":false,"metadata":{"code_hash":"e47ad011c33c","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.airtable_composio.ComposioAirtableAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioAirtableAPIComponent(ComposioBaseComponent):\n display_name: str = \"Airtable\"\n icon = \"Airtable\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"airtable\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Airtable component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioApolloAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Apollo","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Apollo","legacy":false,"metadata":{"code_hash":"3af16f5d6ceb","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.apollo_composio.ComposioApolloAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioApolloAPIComponent(ComposioBaseComponent):\n display_name: str = \"Apollo\"\n icon = \"Apollo\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"apollo\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Apollo component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioAsanaAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Asana","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Asana","legacy":false,"metadata":{"code_hash":"290d6d61d049","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.asana_composio.ComposioAsanaAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioAsanaAPIComponent(ComposioBaseComponent):\n display_name: str = \"Asana\"\n icon = \"Asana\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"asana\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Asana component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioAttioAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Attio","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Attio","legacy":false,"metadata":{"code_hash":"de43b3cf5671","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.attio_composio.ComposioAttioAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioAttioAPIComponent(ComposioBaseComponent):\n display_name: str = \"Attio\"\n icon = \"Attio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"attio\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Attio component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioBitbucketAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Bitbucket","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Bitbucket","legacy":false,"metadata":{"code_hash":"7528a8928646","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.bitbucket_composio.ComposioBitbucketAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioBitbucketAPIComponent(ComposioBaseComponent):\n display_name: str = \"Bitbucket\"\n icon = \"Bitbucket\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"bitbucket\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Bitbucket component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioBolnaAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Bolna","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Bolna","legacy":false,"metadata":{"code_hash":"dde7d2ee80a2","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.bolna_composio.ComposioBolnaAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioBolnaAPIComponent(ComposioBaseComponent):\n display_name: str = \"Bolna\"\n icon = \"Bolna\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"bolna\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Bolna component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioBrightdataAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Brightdata","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Brightdata","legacy":false,"metadata":{"code_hash":"49a04c5a23cb","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.brightdata_composio.ComposioBrightdataAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioBrightdataAPIComponent(ComposioBaseComponent):\n display_name: str = \"Brightdata\"\n icon = \"Brightdata\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"brightdata\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Brightdata component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioCalendlyAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Calendly","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Calendly","legacy":false,"metadata":{"code_hash":"4a282e413d55","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.calendly_composio.ComposioCalendlyAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioCalendlyAPIComponent(ComposioBaseComponent):\n display_name: str = \"Calendly\"\n icon = \"Calendly\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"calendly\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Calendly component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioCanvaAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Canva","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Canva","legacy":false,"metadata":{"code_hash":"d149aa178e80","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.canva_composio.ComposioCanvaAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioCanvaAPIComponent(ComposioBaseComponent):\n display_name: str = \"Canva\"\n icon = \"Canva\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"canva\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Canva component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioCanvasAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Canvas","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Canvas","legacy":false,"metadata":{"code_hash":"6510d212a720","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.canvas_composio.ComposioCanvasAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioCanvasAPIComponent(ComposioBaseComponent):\n display_name: str = \"Canvas\"\n icon = \"Canvas\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"canvas\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Canvaas component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioCodaAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Coda","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Coda","legacy":false,"metadata":{"code_hash":"f7693920313f","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.coda_composio.ComposioCodaAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioCodaAPIComponent(ComposioBaseComponent):\n display_name: str = \"Coda\"\n icon = \"Coda\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"coda\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Coda component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioContentfulAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Contentful","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Contentful","legacy":false,"metadata":{"code_hash":"36befb1ec8fc","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.contentful_composio.ComposioContentfulAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioContentfulAPIComponent(ComposioBaseComponent):\n display_name: str = \"Contentful\"\n icon = \"Contentful\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"contentful\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Contentful component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioDigicertAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Digicert","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Digicert","legacy":false,"metadata":{"code_hash":"0fcbc1b899f8","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.digicert_composio.ComposioDigicertAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioDigicertAPIComponent(ComposioBaseComponent):\n display_name: str = \"Digicert\"\n icon = \"Digicert\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"digicert\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Digicert component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioDiscordAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Discord","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"discord","legacy":false,"metadata":{"code_hash":"2ec988f25784","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.discord_composio.ComposioDiscordAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioDiscordAPIComponent(ComposioBaseComponent):\n display_name: str = \"Discord\"\n icon = \"discord\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"discord\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Discord component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioDropboxAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Dropbox","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Dropbox","legacy":false,"metadata":{"code_hash":"d05825599def","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.dropbox_compnent.ComposioDropboxAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioDropboxAPIComponent(ComposioBaseComponent):\n display_name: str = \"Dropbox\"\n icon = \"Dropbox\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"dropbox\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Dropbox component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioElevenLabsAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"ElevenLabs","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Elevenlabs","legacy":false,"metadata":{"code_hash":"e0c91533558b","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.elevenlabs_composio.ComposioElevenLabsAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioElevenLabsAPIComponent(ComposioBaseComponent):\n display_name: str = \"ElevenLabs\"\n icon = \"Elevenlabs\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"elevenlabs\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for ElevenLabs component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioExaAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Exa","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"ExaComposio","legacy":false,"metadata":{"code_hash":"3b5cecdefab8","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.exa_composio.ComposioExaAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioExaAPIComponent(ComposioBaseComponent):\n display_name: str = \"Exa\"\n icon = \"ExaComposio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"exa\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Exa component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioFigmaAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Figma","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Figma","legacy":false,"metadata":{"code_hash":"7443d213546b","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.figma_composio.ComposioFigmaAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioFigmaAPIComponent(ComposioBaseComponent):\n display_name: str = \"Figma\"\n icon = \"Figma\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"figma\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Figma component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioFinageAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Finage","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Finage","legacy":false,"metadata":{"code_hash":"50a2bdee4cd1","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.finage_composio.ComposioFinageAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioFinageAPIComponent(ComposioBaseComponent):\n display_name: str = \"Finage\"\n icon = \"Finage\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"finage\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Finage component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioFirecrawlAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Firecrawl","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Firecrawl","legacy":false,"metadata":{"code_hash":"2ab1c4b00071","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.firecrawl_composio.ComposioFirecrawlAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioFirecrawlAPIComponent(ComposioBaseComponent):\n display_name: str = \"Firecrawl\"\n icon = \"Firecrawl\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"firecrawl\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Firecrawl component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioFirefliesAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Fireflies","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Fireflies","legacy":false,"metadata":{"code_hash":"233cd91dbdad","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.fireflies_composio.ComposioFirefliesAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioFirefliesAPIComponent(ComposioBaseComponent):\n display_name: str = \"Fireflies\"\n icon = \"Fireflies\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"fireflies\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Fireflies component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioFixerAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Fixer","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Fixer","legacy":false,"metadata":{"code_hash":"9e4c00f9dcd8","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.fixer_composio.ComposioFixerAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioFixerAPIComponent(ComposioBaseComponent):\n display_name: str = \"Fixer\"\n icon = \"Fixer\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"fixer\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Fixer component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioFlexisignAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Flexisign","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Flexisign","legacy":false,"metadata":{"code_hash":"c69bbee0005d","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.flexisign_composio.ComposioFlexisignAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioFlexisignAPIComponent(ComposioBaseComponent):\n display_name: str = \"Flexisign\"\n icon = \"Flexisign\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"flexisign\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Flexisign component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioFreshdeskAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Freshdesk","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Freshdesk","legacy":false,"metadata":{"code_hash":"1dde03d615ca","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.freshdesk_composio.ComposioFreshdeskAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioFreshdeskAPIComponent(ComposioBaseComponent):\n display_name: str = \"Freshdesk\"\n icon = \"Freshdesk\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"freshdesk\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Freshdesk component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGitHubAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"GitHub","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"GithubComposio","legacy":false,"metadata":{"code_hash":"ee201105d924","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.github.amrom.workers.devposio.ComposioGitHubAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGitHubAPIComponent(ComposioBaseComponent):\n display_name: str = \"GitHub\"\n icon = \"GithubComposio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"github\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for GitHub component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGmailAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Gmail","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Gmail","legacy":false,"metadata":{"code_hash":"d4b13ac8a3a1","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.gmail_composio.ComposioGmailAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGmailAPIComponent(ComposioBaseComponent):\n display_name: str = \"Gmail\"\n icon = \"Gmail\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"gmail\"\n\n def __init__(self, **kwargs):\n super().__init__(**kwargs)\n self.post_processors = {\n \"GMAIL_SEND_EMAIL\": self._process_send_email_response,\n \"GMAIL_FETCH_EMAILS\": self._process_fetch_emails_response,\n }\n\n def _process_send_email_response(self, raw_data):\n \"\"\"Post-processor for GMAIL_SEND_EMAIL action.\"\"\"\n if isinstance(raw_data, dict):\n response_data = raw_data.get(\"response_data\", raw_data)\n\n return {\n \"message_id\": response_data.get(\"id\"),\n \"thread_id\": response_data.get(\"threadId\"),\n \"label_ids\": response_data.get(\"labelIds\", []),\n }\n return raw_data\n\n def _process_fetch_emails_response(self, raw_data):\n \"\"\"Post-processor for GMAIL_FETCH_EMAILS action.\"\"\"\n if isinstance(raw_data, dict):\n messages = raw_data.get(\"messages\", [])\n if messages:\n return messages\n return raw_data\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Gmail component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGoogleBigQueryAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"GoogleBigQuery","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Googlebigquery","legacy":false,"metadata":{"code_hash":"f7d84aaae78f","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.googlebigquery_composio.ComposioGoogleBigQueryAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGoogleBigQueryAPIComponent(ComposioBaseComponent):\n display_name: str = \"GoogleBigQuery\"\n icon = \"Googlebigquery\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"googlebigquery\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Google BigQuery component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGoogleCalendarAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"GoogleCalendar","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Googlecalendar","legacy":false,"metadata":{"code_hash":"28adb6fff093","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.googlecalendar_composio.ComposioGoogleCalendarAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGoogleCalendarAPIComponent(ComposioBaseComponent):\n display_name: str = \"GoogleCalendar\"\n icon = \"Googlecalendar\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"googlecalendar\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Google Calendar component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGoogleDocsAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"GoogleDocs","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Googledocs","legacy":false,"metadata":{"code_hash":"ac2e88b6f706","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.googledocs_composio.ComposioGoogleDocsAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGoogleDocsAPIComponent(ComposioBaseComponent):\n display_name: str = \"GoogleDocs\"\n icon = \"Googledocs\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"googledocs\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Google Docs component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGoogleSheetsAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"GoogleSheets","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Googlesheets","legacy":false,"metadata":{"code_hash":"b0db7a3abe1f","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.googlesheets_composio.ComposioGoogleSheetsAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGoogleSheetsAPIComponent(ComposioBaseComponent):\n display_name: str = \"GoogleSheets\"\n icon = \"Googlesheets\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"googlesheets\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Google Sheets component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGoogleTasksAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"GoogleTasks","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"GoogleTasks","legacy":false,"metadata":{"code_hash":"2ba9c1661f41","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.googletasks_composio.ComposioGoogleTasksAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGoogleTasksAPIComponent(ComposioBaseComponent):\n display_name: str = \"GoogleTasks\"\n icon = \"GoogleTasks\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"googletasks\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGoogleclassroomAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Google Classroom","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Classroom","legacy":false,"metadata":{"code_hash":"85a5c37c13f6","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.googleclassroom_composio.ComposioGoogleclassroomAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGoogleclassroomAPIComponent(ComposioBaseComponent):\n display_name: str = \"Google Classroom\"\n icon = \"Classroom\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"GOOGLE_CLASSROOM\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Google Classroom component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioGooglemeetAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"GoogleMeet","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Googlemeet","legacy":false,"metadata":{"code_hash":"cdbf16c4b42f","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.googlemeet_composio.ComposioGooglemeetAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioGooglemeetAPIComponent(ComposioBaseComponent):\n display_name: str = \"GoogleMeet\"\n icon = \"Googlemeet\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"googlemeet\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Google Calendar component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioHeygenAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Heygen","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Heygen","legacy":false,"metadata":{"code_hash":"c72fd5d0350f","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.heygen_composio.ComposioHeygenAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioHeygenAPIComponent(ComposioBaseComponent):\n display_name: str = \"Heygen\"\n icon = \"Heygen\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"heygen\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Heygen component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioInstagramAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Instagram","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Instagram","legacy":false,"metadata":{"code_hash":"a6691c905833","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.instagram_composio.ComposioInstagramAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioInstagramAPIComponent(ComposioBaseComponent):\n display_name: str = \"Instagram\"\n icon = \"Instagram\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"instagram\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Instagram component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioJiraAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Jira","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Jira","legacy":false,"metadata":{"code_hash":"3e62396f3868","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.jira_composio.ComposioJiraAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioJiraAPIComponent(ComposioBaseComponent):\n display_name: str = \"Jira\"\n icon = \"Jira\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"jira\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Jira component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioJotformAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Jotform","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Jotform","legacy":false,"metadata":{"code_hash":"7c1c6a676814","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.jotform_composio.ComposioJotformAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioJotformAPIComponent(ComposioBaseComponent):\n display_name: str = \"Jotform\"\n icon = \"Jotform\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"jotform\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Jotform component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioKlaviyoAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Klaviyo","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Klaviyo","legacy":false,"metadata":{"code_hash":"3be7e8a5e3fe","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.klaviyo_composio.ComposioKlaviyoAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioKlaviyoAPIComponent(ComposioBaseComponent):\n display_name: str = \"Klaviyo\"\n icon = \"Klaviyo\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"klaviyo\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Klaviyo component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioLinearAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Linear","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Linear","legacy":false,"metadata":{"code_hash":"be2b2ebbeea7","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.linear_composio.ComposioLinearAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioLinearAPIComponent(ComposioBaseComponent):\n display_name: str = \"Linear\"\n icon = \"Linear\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"linear\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Linear component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioListennotesAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Listennotes","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Listennotes","legacy":false,"metadata":{"code_hash":"b85f2fe51906","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.listennotes_composio.ComposioListennotesAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioListennotesAPIComponent(ComposioBaseComponent):\n display_name: str = \"Listennotes\"\n icon = \"Listennotes\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"listennotes\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Listennotes component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioMem0APIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Mem0","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Mem0Composio","legacy":false,"metadata":{"code_hash":"68871a483786","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.mem0_composio.ComposioMem0APIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioMem0APIComponent(ComposioBaseComponent):\n display_name: str = \"Mem0\"\n icon = \"Mem0Composio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"mem0\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Mem0 component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioMiroAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Miro","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Miro","legacy":false,"metadata":{"code_hash":"1e9c421e1ac4","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.miro_composio.ComposioMiroAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioMiroAPIComponent(ComposioBaseComponent):\n display_name: str = \"Miro\"\n icon = \"Miro\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"miro\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Miro component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioMissiveAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Missive","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Missive","legacy":false,"metadata":{"code_hash":"6def944a7739","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.missive_composio.ComposioMissiveAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioMissiveAPIComponent(ComposioBaseComponent):\n display_name: str = \"Missive\"\n icon = \"Missive\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"missive\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Missive component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioNotionAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Notion","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Notion","legacy":false,"metadata":{"code_hash":"590aa6ff30d1","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.notion_composio.ComposioNotionAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioNotionAPIComponent(ComposioBaseComponent):\n display_name: str = \"Notion\"\n icon = \"Notion\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"notion\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Notion component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioOneDriveAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"OneDrive","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"One_Drive","legacy":false,"metadata":{"code_hash":"497cc4625121","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.onedrive_composio.ComposioOneDriveAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioOneDriveAPIComponent(ComposioBaseComponent):\n display_name: str = \"OneDrive\"\n icon = \"One_Drive\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"one_drive\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for OneDrive component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioOutlookAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Outlook","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Outlook","legacy":false,"metadata":{"code_hash":"bf6998d60b63","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.outlook_composio.ComposioOutlookAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioOutlookAPIComponent(ComposioBaseComponent):\n display_name: str = \"Outlook\"\n icon = \"Outlook\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"outlook\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Gmail component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioPandadocAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Pandadoc","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Pandadoc","legacy":false,"metadata":{"code_hash":"21d92aabc1bf","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.pandadoc_composio.ComposioPandadocAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioPandadocAPIComponent(ComposioBaseComponent):\n display_name: str = \"Pandadoc\"\n icon = \"Pandadoc\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"pandadoc\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Pandadoc component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioPeopleDataLabsAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"PeopleDataLabs","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Peopledatalabs","legacy":false,"metadata":{"code_hash":"bd05ce58f55c","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.peopledatalabs_composio.ComposioPeopleDataLabsAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioPeopleDataLabsAPIComponent(ComposioBaseComponent):\n display_name: str = \"PeopleDataLabs\"\n icon = \"Peopledatalabs\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"peopledatalabs\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for PeopleDataLabs component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioPerplexityAIAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"PerplexityAI","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"PerplexityComposio","legacy":false,"metadata":{"code_hash":"e40b0651344f","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.perplexityai_composio.ComposioPerplexityAIAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioPerplexityAIAPIComponent(ComposioBaseComponent):\n display_name: str = \"PerplexityAI\"\n icon = \"PerplexityComposio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"perplexityai\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for PerplexityAI component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioRedditAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Reddit","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Reddit","legacy":false,"metadata":{"code_hash":"a86794073c22","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.reddit_composio.ComposioRedditAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioRedditAPIComponent(ComposioBaseComponent):\n display_name: str = \"Reddit\"\n icon = \"Reddit\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"reddit\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Reddit component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioSerpAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"SerpAPI","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"SerpSearchComposio","legacy":false,"metadata":{"code_hash":"74b0a07ee54b","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.serpapi_composio.ComposioSerpAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioSerpAPIComponent(ComposioBaseComponent):\n display_name: str = \"SerpAPI\"\n icon = \"SerpSearchComposio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"serpapi\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for SerpAPI component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioSlackAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Slack","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"SlackComposio","legacy":false,"metadata":{"code_hash":"fa340cae1330","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.slack_composio.ComposioSlackAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioSlackAPIComponent(ComposioBaseComponent):\n display_name: str = \"Slack\"\n icon = \"SlackComposio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"slack\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Slack component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioSlackbotAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Slackbot","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"SlackComposio","legacy":false,"metadata":{"code_hash":"ddeb26bc04e6","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.slackbot_composio.ComposioSlackbotAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioSlackbotAPIComponent(ComposioBaseComponent):\n display_name: str = \"Slackbot\"\n icon = \"SlackComposio\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"slackbot\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Slackbot component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioSnowflakeAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Snowflake","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Snowflake","legacy":false,"metadata":{"code_hash":"d0d1af5686d2","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.snowflake_composio.ComposioSnowflakeAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioSnowflakeAPIComponent(ComposioBaseComponent):\n display_name: str = \"Snowflake\"\n icon = \"Snowflake\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"snowflake\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Snowflake component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioSupabaseAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Supabase","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Supabase","legacy":false,"metadata":{"code_hash":"7ad58ce34cc0","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.supabase_composio.ComposioSupabaseAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioSupabaseAPIComponent(ComposioBaseComponent):\n display_name: str = \"Supabase\"\n icon = \"Supabase\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"supabase\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Supabase component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioTavilyAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Tavily","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Tavily","legacy":false,"metadata":{"code_hash":"97af05e37911","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.tavily_composio.ComposioTavilyAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioTavilyAPIComponent(ComposioBaseComponent):\n display_name: str = \"Tavily\"\n icon = \"Tavily\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"tavily\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Tavily component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioTimelinesAIAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"TimelinesAI","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Timelinesai","legacy":false,"metadata":{"code_hash":"76e70e2de4d3","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.timelinesai_composio.ComposioTimelinesAIAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioTimelinesAIAPIComponent(ComposioBaseComponent):\n display_name: str = \"TimelinesAI\"\n icon = \"Timelinesai\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"timelinesai\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for TimelinesAI component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioTodoistAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Todoist","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Todoist","legacy":false,"metadata":{"code_hash":"4dd9852f2058","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.todoist_composio.ComposioTodoistAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioTodoistAPIComponent(ComposioBaseComponent):\n display_name: str = \"Todoist\"\n icon = \"Todoist\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"todoist\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Todoist component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioWrikeAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"Wrike","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"Wrike","legacy":false,"metadata":{"code_hash":"a5f2cf00ca08","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.wrike_composio.ComposioWrikeAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioWrikeAPIComponent(ComposioBaseComponent):\n display_name: str = \"Wrike\"\n icon = \"Wrike\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"wrike\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Wrike component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"ComposioYoutubeAPIComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"display_name":"YouTube","documentation":"https://docs.composio.dev","edited":false,"field_order":["entity_id","api_key","auth_mode","auth_link","client_id","client_secret","verification_token","redirect_uri","authorization_url","token_url","api_key_field","generic_api_key","token","access_token","refresh_token","username","password","domain","base_url","bearer_token","authorization_code","scopes","subdomain","instance_url","tenant_id","action_button"],"frozen":false,"icon":"YouTube","legacy":false,"metadata":{"code_hash":"d1af2ea00e8b","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.composio.youtube_composio.ComposioYoutubeAPIComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataFrame","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","access_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Access Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"access_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"action_button":{"_input_type":"SortableListInput","advanced":false,"display_name":"Action","dynamic":false,"helper_text":"Please connect before selecting actions.","helper_text_metadata":{"variant":"destructive"},"info":"","limit":1,"name":"action_button","options":[],"override_skip":false,"placeholder":"Select action","real_time_refresh":true,"required":false,"search_category":[],"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"sortableList","value":"disabled"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Composio API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"COMPOSIO_API_KEY"},"api_key_field":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"api_key_field","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"auth_link":{"_input_type":"AuthInput","advanced":false,"auth_tooltip":"Please insert a valid Composio API Key.","dynamic":false,"info":"","name":"auth_link","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"auth","value":""},"auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Auth Mode","dynamic":false,"external_options":{},"helper_text":"Choose how to authenticate with the toolkit.","info":"","name":"auth_mode","options":[],"options_metadata":[],"override_skip":false,"placeholder":"Select auth mode","real_time_refresh":true,"required":false,"show":false,"title_case":false,"toggle":true,"toggle_disable":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"authorization_code":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Authorization Code","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"authorization_code","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"authorization_url":{"_input_type":"StrInput","advanced":false,"display_name":"Authorization URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"authorization_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"base_url":{"_input_type":"StrInput","advanced":false,"display_name":"Base URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"base_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"bearer_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Bearer Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"bearer_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client ID","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_id","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"client_secret":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Client Secret","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"client_secret","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"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":"from lfx.base.composio.composio_base import ComposioBaseComponent\n\n\nclass ComposioYoutubeAPIComponent(ComposioBaseComponent):\n display_name: str = \"YouTube\"\n icon = \"YouTube\"\n documentation: str = \"https://docs.composio.dev\"\n app_name = \"youtube\"\n\n def set_default_tools(self):\n \"\"\"Set the default tools for Youtube component.\"\"\"\n"},"domain":{"_input_type":"StrInput","advanced":false,"display_name":"Domain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"domain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"entity_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Entity ID","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"entity_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default"},"generic_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"API Key","dynamic":false,"info":"Enter API key on Composio page","input_types":[],"load_from_db":true,"name":"generic_api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"instance_url":{"_input_type":"StrInput","advanced":false,"display_name":"Instance URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instance_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"redirect_uri":{"_input_type":"StrInput","advanced":false,"display_name":"Redirect URI","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"redirect_uri","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"refresh_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Refresh Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"refresh_token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"scopes":{"_input_type":"StrInput","advanced":false,"display_name":"Scopes","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scopes","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"subdomain":{"_input_type":"StrInput","advanced":false,"display_name":"Subdomain","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"subdomain","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tenant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Tenant ID","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tenant_id","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Token","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"token_url":{"_input_type":"StrInput","advanced":false,"display_name":"Token URL","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"token_url","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verification_token":{"_input_type":"StrInput","advanced":false,"display_name":"Verification Token","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"verification_token","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false}}],["confluence",{"Confluence":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Confluence wiki collaboration platform","display_name":"Confluence","documentation":"https://python.langchain.com/v0.2/docs/integrations/document_loaders/confluence/","edited":false,"field_order":["url","username","api_key","space_key","cloud","content_format","max_pages"],"frozen":false,"icon":"Confluence","legacy":false,"metadata":{"code_hash":"8a7ef34b66e4","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.confluence.confluence.ConfluenceComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"load_documents","name":"data","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Confluence API Key","dynamic":false,"info":"Atlassian Key. Create at: https://id.atlassian.com/manage-profile/security/api-tokens","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":""},"cloud":{"_input_type":"BoolInput","advanced":true,"display_name":"Use Cloud?","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"cloud","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from langchain_community.document_loaders import ConfluenceLoader\nfrom langchain_community.document_loaders.confluence import ContentFormat\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DropdownInput, IntInput, Output, SecretStrInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass ConfluenceComponent(Component):\n display_name = \"Confluence\"\n description = \"Confluence wiki collaboration platform\"\n documentation = \"https://python.langchain.com/v0.2/docs/integrations/document_loaders/confluence/\"\n trace_type = \"tool\"\n icon = \"Confluence\"\n name = \"Confluence\"\n\n inputs = [\n StrInput(\n name=\"url\",\n display_name=\"Site URL\",\n required=True,\n info=\"The base URL of the Confluence Space. Example: https://.atlassian.net/wiki.\",\n ),\n StrInput(\n name=\"username\",\n display_name=\"Username\",\n required=True,\n info=\"Atlassian User E-mail. Example: email@example.com\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Confluence API Key\",\n required=True,\n info=\"Atlassian Key. Create at: https://id.atlassian.com/manage-profile/security/api-tokens\",\n ),\n StrInput(name=\"space_key\", display_name=\"Space Key\", required=True),\n BoolInput(name=\"cloud\", display_name=\"Use Cloud?\", required=True, value=True, advanced=True),\n DropdownInput(\n name=\"content_format\",\n display_name=\"Content Format\",\n options=[\n ContentFormat.EDITOR.value,\n ContentFormat.EXPORT_VIEW.value,\n ContentFormat.ANONYMOUS_EXPORT_VIEW.value,\n ContentFormat.STORAGE.value,\n ContentFormat.VIEW.value,\n ],\n value=ContentFormat.STORAGE.value,\n required=True,\n advanced=True,\n info=\"Specify content format, defaults to ContentFormat.STORAGE\",\n ),\n IntInput(\n name=\"max_pages\",\n display_name=\"Max Pages\",\n required=False,\n value=1000,\n advanced=True,\n info=\"Maximum number of pages to retrieve in total, defaults 1000\",\n ),\n ]\n\n outputs = [\n Output(name=\"data\", display_name=\"Data\", method=\"load_documents\"),\n ]\n\n def build_confluence(self) -> ConfluenceLoader:\n content_format = ContentFormat(self.content_format)\n return ConfluenceLoader(\n url=self.url,\n username=self.username,\n api_key=self.api_key,\n cloud=self.cloud,\n space_key=self.space_key,\n content_format=content_format,\n max_pages=self.max_pages,\n )\n\n def load_documents(self) -> list[Data]:\n confluence = self.build_confluence()\n documents = confluence.load()\n data = [Data.from_document(doc) for doc in documents] # Using the from_document method of Data\n self.status = data\n return data\n"},"content_format":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Content Format","dynamic":false,"external_options":{},"info":"Specify content format, defaults to ContentFormat.STORAGE","name":"content_format","options":["body.editor","body.export_view","body.anonymous_export_view","body.storage","body.view"],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"body.storage"},"max_pages":{"_input_type":"IntInput","advanced":true,"display_name":"Max Pages","dynamic":false,"info":"Maximum number of pages to retrieve in total, defaults 1000","list":false,"list_add_label":"Add More","name":"max_pages","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":1000},"space_key":{"_input_type":"StrInput","advanced":false,"display_name":"Space Key","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"space_key","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"url":{"_input_type":"StrInput","advanced":false,"display_name":"Site URL","dynamic":false,"info":"The base URL of the Confluence Space. Example: https://.atlassian.net/wiki.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"url","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"Atlassian User E-mail. Example: email@example.com","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false}}],["couchbase",{"Couchbase":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Couchbase Vector Store with search capabilities","display_name":"Couchbase","documentation":"","edited":false,"field_order":["couchbase_connection_string","couchbase_username","couchbase_password","bucket_name","scope_name","collection_name","index_name","ingest_data","search_query","should_cache_vector_store","embedding","number_of_results"],"frozen":false,"icon":"Couchbase","legacy":false,"metadata":{"code_hash":"70ed475a6f48","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null},{"name":"couchbase","version":null}],"total_dependencies":3},"module":"lfx.components.couchbase.couchbase.CouchbaseVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","bucket_name":{"_input_type":"StrInput","advanced":false,"display_name":"Bucket Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"bucket_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"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":"from datetime import timedelta\n\nfrom langchain_community.vectorstores import CouchbaseVectorStore\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.io import HandleInput, IntInput, SecretStrInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass CouchbaseVectorStoreComponent(LCVectorStoreComponent):\n display_name = \"Couchbase\"\n description = \"Couchbase Vector Store with search capabilities\"\n name = \"Couchbase\"\n icon = \"Couchbase\"\n\n inputs = [\n SecretStrInput(\n name=\"couchbase_connection_string\", display_name=\"Couchbase Cluster connection string\", required=True\n ),\n StrInput(name=\"couchbase_username\", display_name=\"Couchbase username\", required=True),\n SecretStrInput(name=\"couchbase_password\", display_name=\"Couchbase password\", required=True),\n StrInput(name=\"bucket_name\", display_name=\"Bucket Name\", required=True),\n StrInput(name=\"scope_name\", display_name=\"Scope Name\", required=True),\n StrInput(name=\"collection_name\", display_name=\"Collection Name\", required=True),\n StrInput(name=\"index_name\", display_name=\"Index Name\", required=True),\n *LCVectorStoreComponent.inputs,\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n value=4,\n advanced=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self) -> CouchbaseVectorStore:\n try:\n from couchbase.auth import PasswordAuthenticator\n from couchbase.cluster import Cluster\n from couchbase.options import ClusterOptions\n except ImportError as e:\n msg = \"Failed to import Couchbase dependencies. Install it using `uv pip install langflow[couchbase] --pre`\"\n raise ImportError(msg) from e\n\n try:\n auth = PasswordAuthenticator(self.couchbase_username, self.couchbase_password)\n options = ClusterOptions(auth)\n cluster = Cluster(self.couchbase_connection_string, options)\n\n cluster.wait_until_ready(timedelta(seconds=5))\n except Exception as e:\n msg = f\"Failed to connect to Couchbase: {e}\"\n raise ValueError(msg) from e\n\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n if documents:\n couchbase_vs = CouchbaseVectorStore.from_documents(\n documents=documents,\n cluster=cluster,\n bucket_name=self.bucket_name,\n scope_name=self.scope_name,\n collection_name=self.collection_name,\n embedding=self.embedding,\n index_name=self.index_name,\n )\n\n else:\n couchbase_vs = CouchbaseVectorStore(\n cluster=cluster,\n bucket_name=self.bucket_name,\n scope_name=self.scope_name,\n collection_name=self.collection_name,\n embedding=self.embedding,\n index_name=self.index_name,\n )\n\n return couchbase_vs\n\n def search_documents(self) -> list[Data]:\n vector_store = self.build_vector_store()\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n docs = vector_store.similarity_search(\n query=self.search_query,\n k=self.number_of_results,\n )\n\n data = docs_to_data(docs)\n self.status = data\n return data\n return []\n"},"collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Collection Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"collection_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"couchbase_connection_string":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Couchbase Cluster connection string","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"couchbase_connection_string","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"couchbase_password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Couchbase password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"couchbase_password","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"couchbase_username":{"_input_type":"StrInput","advanced":false,"display_name":"Couchbase username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"couchbase_username","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"index_name":{"_input_type":"StrInput","advanced":false,"display_name":"Index Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"index_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"scope_name":{"_input_type":"StrInput","advanced":false,"display_name":"Scope Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"scope_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false}}],["crewai",{"CrewAIAgentComponent":{"base_classes":["NoneType"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Represents an agent of CrewAI.","display_name":"CrewAI Agent","documentation":"https://docs.crewai.com/how-to/LLM-Connections/","edited":false,"field_order":["role","goal","backstory","tools","llm","memory","verbose","allow_delegation","allow_code_execution","kwargs"],"frozen":false,"icon":"CrewAI","legacy":true,"metadata":{"code_hash":"a23f0923049d","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"crewai","version":null}],"total_dependencies":2},"module":"lfx.components.crewai.crewai.CrewAIAgentComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Agent","group_outputs":false,"method":"build_output","name":"output","selected":"NoneType","tool_mode":true,"types":["NoneType"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","allow_code_execution":{"_input_type":"BoolInput","advanced":true,"display_name":"Allow Code Execution","dynamic":false,"info":"Whether the agent is allowed to execute code.","list":false,"list_add_label":"Add More","name":"allow_code_execution","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"allow_delegation":{"_input_type":"BoolInput","advanced":false,"display_name":"Allow Delegation","dynamic":false,"info":"Whether the agent is allowed to delegate tasks to other agents.","list":false,"list_add_label":"Add More","name":"allow_delegation","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"backstory":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Backstory","dynamic":false,"info":"The backstory of the agent.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"backstory","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":""},"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":"from lfx.base.agents.crewai.crew import convert_llm, convert_tools\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DictInput, HandleInput, MultilineInput, Output\n\n\nclass CrewAIAgentComponent(Component):\n \"\"\"Component for creating a CrewAI agent.\n\n This component allows you to create a CrewAI agent with the specified role, goal, backstory, tools,\n and language model.\n\n Args:\n Component (Component): Base class for all components.\n\n Returns:\n Agent: CrewAI agent.\n \"\"\"\n\n display_name = \"CrewAI Agent\"\n description = \"Represents an agent of CrewAI.\"\n documentation: str = \"https://docs.crewai.com/how-to/LLM-Connections/\"\n icon = \"CrewAI\"\n legacy = True\n replacement = \"agents.Agent\"\n\n inputs = [\n MultilineInput(name=\"role\", display_name=\"Role\", info=\"The role of the agent.\"),\n MultilineInput(name=\"goal\", display_name=\"Goal\", info=\"The objective of the agent.\"),\n MultilineInput(name=\"backstory\", display_name=\"Backstory\", info=\"The backstory of the agent.\"),\n HandleInput(\n name=\"tools\",\n display_name=\"Tools\",\n input_types=[\"Tool\"],\n is_list=True,\n info=\"Tools at agents disposal\",\n value=[],\n ),\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"Language model that will run the agent.\",\n input_types=[\"LanguageModel\"],\n ),\n BoolInput(\n name=\"memory\",\n display_name=\"Memory\",\n info=\"Whether the agent should have memory or not\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"verbose\",\n display_name=\"Verbose\",\n advanced=True,\n value=False,\n ),\n BoolInput(\n name=\"allow_delegation\",\n display_name=\"Allow Delegation\",\n info=\"Whether the agent is allowed to delegate tasks to other agents.\",\n value=True,\n ),\n BoolInput(\n name=\"allow_code_execution\",\n display_name=\"Allow Code Execution\",\n info=\"Whether the agent is allowed to execute code.\",\n value=False,\n advanced=True,\n ),\n DictInput(\n name=\"kwargs\",\n display_name=\"kwargs\",\n info=\"kwargs of agent.\",\n is_list=True,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Agent\", name=\"output\", method=\"build_output\"),\n ]\n\n def build_output(self):\n try:\n from crewai import Agent\n except ImportError as e:\n msg = \"CrewAI is not installed. Please install it with `uv pip install crewai`.\"\n raise ImportError(msg) from e\n\n kwargs = self.kwargs or {}\n\n # Define the Agent\n agent = Agent(\n role=self.role,\n goal=self.goal,\n backstory=self.backstory,\n llm=convert_llm(self.llm),\n verbose=self.verbose,\n memory=self.memory,\n tools=convert_tools(self.tools),\n allow_delegation=self.allow_delegation,\n allow_code_execution=self.allow_code_execution,\n **kwargs,\n )\n\n self.status = repr(agent)\n\n return agent\n"},"goal":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Goal","dynamic":false,"info":"The objective of the agent.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"goal","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":""},"kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"kwargs","dynamic":false,"info":"kwargs of agent.","list":true,"list_add_label":"Add More","name":"kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"llm":{"_input_type":"HandleInput","advanced":false,"display_name":"Language Model","dynamic":false,"info":"Language model that will run the agent.","input_types":["LanguageModel"],"list":false,"list_add_label":"Add More","name":"llm","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"memory":{"_input_type":"BoolInput","advanced":true,"display_name":"Memory","dynamic":false,"info":"Whether the agent should have memory or not","list":false,"list_add_label":"Add More","name":"memory","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"role":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Role","dynamic":false,"info":"The role of the agent.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"role","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":""},"tools":{"_input_type":"HandleInput","advanced":false,"display_name":"Tools","dynamic":false,"info":"Tools at agents disposal","input_types":["Tool"],"list":true,"list_add_label":"Add More","name":"tools","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":[]},"verbose":{"_input_type":"BoolInput","advanced":true,"display_name":"Verbose","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"verbose","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"HierarchicalCrewComponent":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Represents a group of agents, defining how they should collaborate and the tasks they should perform.","display_name":"Hierarchical Crew","documentation":"https://docs.crewai.com/how-to/Hierarchical/","edited":false,"field_order":["verbose","memory","use_cache","max_rpm","share_crew","function_calling_llm","agents","tasks","manager_llm","manager_agent"],"frozen":false,"icon":"CrewAI","legacy":true,"metadata":{"code_hash":"144be482cfb0","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"crewai","version":null}],"total_dependencies":2},"module":"lfx.components.crewai.hierarchical_crew.HierarchicalCrewComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Output","group_outputs":false,"method":"build_output","name":"output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","agents":{"_input_type":"HandleInput","advanced":false,"display_name":"Agents","dynamic":false,"info":"","input_types":["Agent"],"list":true,"list_add_label":"Add More","name":"agents","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","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":"from lfx.base.agents.crewai.crew import BaseCrewComponent\nfrom lfx.io import HandleInput\n\n\nclass HierarchicalCrewComponent(BaseCrewComponent):\n display_name: str = \"Hierarchical Crew\"\n description: str = (\n \"Represents a group of agents, defining how they should collaborate and the tasks they should perform.\"\n )\n documentation: str = \"https://docs.crewai.com/how-to/Hierarchical/\"\n icon = \"CrewAI\"\n legacy = True\n replacement = \"agents.Agent\"\n\n inputs = [\n *BaseCrewComponent.get_base_inputs(),\n HandleInput(name=\"agents\", display_name=\"Agents\", input_types=[\"Agent\"], is_list=True),\n HandleInput(name=\"tasks\", display_name=\"Tasks\", input_types=[\"HierarchicalTask\"], is_list=True),\n HandleInput(name=\"manager_llm\", display_name=\"Manager LLM\", input_types=[\"LanguageModel\"], required=False),\n HandleInput(name=\"manager_agent\", display_name=\"Manager Agent\", input_types=[\"Agent\"], required=False),\n ]\n\n def build_crew(self):\n try:\n from crewai import Crew, Process\n except ImportError as e:\n msg = \"CrewAI is not installed. Please install it with `uv pip install crewai`.\"\n raise ImportError(msg) from e\n\n tasks, agents = self.get_tasks_and_agents()\n manager_llm = self.get_manager_llm()\n\n return Crew(\n agents=agents,\n tasks=tasks,\n process=Process.hierarchical,\n verbose=self.verbose,\n memory=self.memory,\n cache=self.use_cache,\n max_rpm=self.max_rpm,\n share_crew=self.share_crew,\n function_calling_llm=self.function_calling_llm,\n manager_agent=self.manager_agent,\n manager_llm=manager_llm,\n step_callback=self.get_step_callback(),\n task_callback=self.get_task_callback(),\n )\n"},"function_calling_llm":{"_input_type":"HandleInput","advanced":true,"display_name":"Function Calling LLM","dynamic":false,"info":"Turns the ReAct CrewAI agent into a function-calling agent","input_types":["LanguageModel"],"list":false,"list_add_label":"Add More","name":"function_calling_llm","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"manager_agent":{"_input_type":"HandleInput","advanced":false,"display_name":"Manager Agent","dynamic":false,"info":"","input_types":["Agent"],"list":false,"list_add_label":"Add More","name":"manager_agent","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"manager_llm":{"_input_type":"HandleInput","advanced":false,"display_name":"Manager LLM","dynamic":false,"info":"","input_types":["LanguageModel"],"list":false,"list_add_label":"Add More","name":"manager_llm","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"max_rpm":{"_input_type":"IntInput","advanced":true,"display_name":"Max RPM","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"max_rpm","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":100},"memory":{"_input_type":"BoolInput","advanced":true,"display_name":"Memory","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"memory","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"share_crew":{"_input_type":"BoolInput","advanced":true,"display_name":"Share Crew","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"share_crew","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"tasks":{"_input_type":"HandleInput","advanced":false,"display_name":"Tasks","dynamic":false,"info":"","input_types":["HierarchicalTask"],"list":true,"list_add_label":"Add More","name":"tasks","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"use_cache":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"use_cache","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"verbose":{"_input_type":"IntInput","advanced":true,"display_name":"Verbose","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"verbose","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":0}},"tool_mode":false},"HierarchicalTaskComponent":{"base_classes":["HierarchicalTask"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Each task must have a description, an expected output and an agent responsible for execution.","display_name":"Hierarchical Task","documentation":"","edited":false,"field_order":["task_description","expected_output","tools"],"frozen":false,"icon":"CrewAI","legacy":true,"metadata":{"code_hash":"25071652dc20","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.crewai.hierarchical_task.HierarchicalTaskComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Task","group_outputs":false,"method":"build_task","name":"task_output","selected":"HierarchicalTask","tool_mode":true,"types":["HierarchicalTask"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","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":"from lfx.base.agents.crewai.tasks import HierarchicalTask\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import HandleInput, MultilineInput, Output\n\n\nclass HierarchicalTaskComponent(Component):\n display_name: str = \"Hierarchical Task\"\n description: str = \"Each task must have a description, an expected output and an agent responsible for execution.\"\n icon = \"CrewAI\"\n legacy = True\n replacement = \"agents.Agent\"\n inputs = [\n MultilineInput(\n name=\"task_description\",\n display_name=\"Description\",\n info=\"Descriptive text detailing task's purpose and execution.\",\n ),\n MultilineInput(\n name=\"expected_output\",\n display_name=\"Expected Output\",\n info=\"Clear definition of expected task outcome.\",\n ),\n HandleInput(\n name=\"tools\",\n display_name=\"Tools\",\n input_types=[\"Tool\"],\n is_list=True,\n info=\"List of tools/resources limited for task execution. Uses the Agent tools by default.\",\n required=False,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Task\", name=\"task_output\", method=\"build_task\"),\n ]\n\n def build_task(self) -> HierarchicalTask:\n task = HierarchicalTask(\n description=self.task_description,\n expected_output=self.expected_output,\n tools=self.tools or [],\n )\n self.status = task\n return task\n"},"expected_output":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Expected Output","dynamic":false,"info":"Clear definition of expected task outcome.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"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":""},"task_description":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Description","dynamic":false,"info":"Descriptive text detailing task's purpose and execution.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"task_description","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":""},"tools":{"_input_type":"HandleInput","advanced":true,"display_name":"Tools","dynamic":false,"info":"List of tools/resources limited for task execution. Uses the Agent tools by default.","input_types":["Tool"],"list":true,"list_add_label":"Add More","name":"tools","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""}},"tool_mode":false},"SequentialCrewComponent":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Represents a group of agents with tasks that are executed sequentially.","display_name":"Sequential Crew","documentation":"https://docs.crewai.com/how-to/Sequential/","edited":false,"field_order":["verbose","memory","use_cache","max_rpm","share_crew","function_calling_llm","tasks"],"frozen":false,"icon":"CrewAI","legacy":true,"metadata":{"code_hash":"42e59f6d6572","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"crewai","version":null}],"total_dependencies":2},"module":"lfx.components.crewai.sequential_crew.SequentialCrewComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Output","group_outputs":false,"method":"build_output","name":"output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","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":"from lfx.base.agents.crewai.crew import BaseCrewComponent\nfrom lfx.io import HandleInput\nfrom lfx.schema.message import Message\n\n\nclass SequentialCrewComponent(BaseCrewComponent):\n display_name: str = \"Sequential Crew\"\n description: str = \"Represents a group of agents with tasks that are executed sequentially.\"\n documentation: str = \"https://docs.crewai.com/how-to/Sequential/\"\n icon = \"CrewAI\"\n legacy = True\n replacement = \"agents.Agent\"\n\n inputs = [\n *BaseCrewComponent.get_base_inputs(),\n HandleInput(name=\"tasks\", display_name=\"Tasks\", input_types=[\"SequentialTask\"], is_list=True),\n ]\n\n @property\n def agents(self: \"SequentialCrewComponent\") -> list:\n # Derive agents directly from linked tasks\n return [task.agent for task in self.tasks if hasattr(task, \"agent\")]\n\n def get_tasks_and_agents(self, agents_list=None) -> tuple[list, list]:\n # Use the agents property to derive agents\n if not agents_list:\n existing_agents = self.agents\n agents_list = existing_agents + (agents_list or [])\n\n return super().get_tasks_and_agents(agents_list=agents_list)\n\n def build_crew(self) -> Message:\n try:\n from crewai import Crew, Process\n except ImportError as e:\n msg = \"CrewAI is not installed. Please install it with `uv pip install crewai`.\"\n raise ImportError(msg) from e\n\n tasks, agents = self.get_tasks_and_agents()\n\n return Crew(\n agents=agents,\n tasks=tasks,\n process=Process.sequential,\n verbose=self.verbose,\n memory=self.memory,\n cache=self.use_cache,\n max_rpm=self.max_rpm,\n share_crew=self.share_crew,\n function_calling_llm=self.function_calling_llm,\n step_callback=self.get_step_callback(),\n task_callback=self.get_task_callback(),\n )\n"},"function_calling_llm":{"_input_type":"HandleInput","advanced":true,"display_name":"Function Calling LLM","dynamic":false,"info":"Turns the ReAct CrewAI agent into a function-calling agent","input_types":["LanguageModel"],"list":false,"list_add_label":"Add More","name":"function_calling_llm","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"max_rpm":{"_input_type":"IntInput","advanced":true,"display_name":"Max RPM","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"max_rpm","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":100},"memory":{"_input_type":"BoolInput","advanced":true,"display_name":"Memory","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"memory","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"share_crew":{"_input_type":"BoolInput","advanced":true,"display_name":"Share Crew","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"share_crew","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"tasks":{"_input_type":"HandleInput","advanced":false,"display_name":"Tasks","dynamic":false,"info":"","input_types":["SequentialTask"],"list":true,"list_add_label":"Add More","name":"tasks","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"use_cache":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"use_cache","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"verbose":{"_input_type":"IntInput","advanced":true,"display_name":"Verbose","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"verbose","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":0}},"tool_mode":false},"SequentialTaskAgentComponent":{"base_classes":["SequentialTask"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Creates a CrewAI Task and its associated Agent.","display_name":"Sequential Task Agent","documentation":"https://docs.crewai.com/how-to/LLM-Connections/","edited":false,"field_order":["role","goal","backstory","tools","llm","memory","verbose","allow_delegation","allow_code_execution","agent_kwargs","task_description","expected_output","async_execution","previous_task"],"frozen":false,"icon":"CrewAI","legacy":true,"metadata":{"code_hash":"0a5483ef82c3","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"crewai","version":null}],"total_dependencies":2},"module":"lfx.components.crewai.sequential_task_agent.SequentialTaskAgentComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Sequential Task","group_outputs":false,"method":"build_agent_and_task","name":"task_output","selected":"SequentialTask","tool_mode":true,"types":["SequentialTask"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","agent_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Agent kwargs","dynamic":false,"info":"Additional kwargs for the agent.","list":true,"list_add_label":"Add More","name":"agent_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"allow_code_execution":{"_input_type":"BoolInput","advanced":true,"display_name":"Allow Code Execution","dynamic":false,"info":"Whether the agent is allowed to execute code.","list":false,"list_add_label":"Add More","name":"allow_code_execution","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"allow_delegation":{"_input_type":"BoolInput","advanced":true,"display_name":"Allow Delegation","dynamic":false,"info":"Whether the agent is allowed to delegate tasks to other agents.","list":false,"list_add_label":"Add More","name":"allow_delegation","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"async_execution":{"_input_type":"BoolInput","advanced":true,"display_name":"Async Execution","dynamic":false,"info":"Boolean flag indicating asynchronous task execution.","list":false,"list_add_label":"Add More","name":"async_execution","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"backstory":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Backstory","dynamic":false,"info":"The backstory of the agent.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"backstory","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":""},"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":"from lfx.base.agents.crewai.tasks import SequentialTask\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DictInput, HandleInput, MultilineInput, Output\n\n\nclass SequentialTaskAgentComponent(Component):\n display_name = \"Sequential Task Agent\"\n description = \"Creates a CrewAI Task and its associated Agent.\"\n documentation = \"https://docs.crewai.com/how-to/LLM-Connections/\"\n icon = \"CrewAI\"\n legacy = True\n replacement = \"agents.Agent\"\n\n inputs = [\n # Agent inputs\n MultilineInput(name=\"role\", display_name=\"Role\", info=\"The role of the agent.\"),\n MultilineInput(name=\"goal\", display_name=\"Goal\", info=\"The objective of the agent.\"),\n MultilineInput(\n name=\"backstory\",\n display_name=\"Backstory\",\n info=\"The backstory of the agent.\",\n ),\n HandleInput(\n name=\"tools\",\n display_name=\"Tools\",\n input_types=[\"Tool\"],\n is_list=True,\n info=\"Tools at agent's disposal\",\n value=[],\n ),\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"Language model that will run the agent.\",\n input_types=[\"LanguageModel\"],\n ),\n BoolInput(\n name=\"memory\",\n display_name=\"Memory\",\n info=\"Whether the agent should have memory or not\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"verbose\",\n display_name=\"Verbose\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"allow_delegation\",\n display_name=\"Allow Delegation\",\n info=\"Whether the agent is allowed to delegate tasks to other agents.\",\n value=False,\n advanced=True,\n ),\n BoolInput(\n name=\"allow_code_execution\",\n display_name=\"Allow Code Execution\",\n info=\"Whether the agent is allowed to execute code.\",\n value=False,\n advanced=True,\n ),\n DictInput(\n name=\"agent_kwargs\",\n display_name=\"Agent kwargs\",\n info=\"Additional kwargs for the agent.\",\n is_list=True,\n advanced=True,\n ),\n # Task inputs\n MultilineInput(\n name=\"task_description\",\n display_name=\"Task Description\",\n info=\"Descriptive text detailing task's purpose and execution.\",\n ),\n MultilineInput(\n name=\"expected_output\",\n display_name=\"Expected Task Output\",\n info=\"Clear definition of expected task outcome.\",\n ),\n BoolInput(\n name=\"async_execution\",\n display_name=\"Async Execution\",\n value=False,\n advanced=True,\n info=\"Boolean flag indicating asynchronous task execution.\",\n ),\n # Chaining input\n HandleInput(\n name=\"previous_task\",\n display_name=\"Previous Task\",\n input_types=[\"SequentialTask\"],\n info=\"The previous task in the sequence (for chaining).\",\n required=False,\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Sequential Task\",\n name=\"task_output\",\n method=\"build_agent_and_task\",\n ),\n ]\n\n def build_agent_and_task(self) -> list[SequentialTask]:\n try:\n from crewai import Agent, Task\n except ImportError as e:\n msg = \"CrewAI is not installed. Please install it with `uv pip install crewai`.\"\n raise ImportError(msg) from e\n\n # Build the agent\n agent_kwargs = self.agent_kwargs or {}\n agent = Agent(\n role=self.role,\n goal=self.goal,\n backstory=self.backstory,\n llm=self.llm,\n verbose=self.verbose,\n memory=self.memory,\n tools=self.tools or [],\n allow_delegation=self.allow_delegation,\n allow_code_execution=self.allow_code_execution,\n **agent_kwargs,\n )\n\n # Build the task\n task = Task(\n description=self.task_description,\n expected_output=self.expected_output,\n agent=agent,\n async_execution=self.async_execution,\n )\n\n # If there's a previous task, create a list of tasks\n if self.previous_task:\n tasks = [*self.previous_task, task] if isinstance(self.previous_task, list) else [self.previous_task, task]\n else:\n tasks = [task]\n\n self.status = f\"Agent: {agent!r}\\nTask: {task!r}\"\n return tasks\n"},"expected_output":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Expected Task Output","dynamic":false,"info":"Clear definition of expected task outcome.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"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":""},"goal":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Goal","dynamic":false,"info":"The objective of the agent.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"goal","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":""},"llm":{"_input_type":"HandleInput","advanced":false,"display_name":"Language Model","dynamic":false,"info":"Language model that will run the agent.","input_types":["LanguageModel"],"list":false,"list_add_label":"Add More","name":"llm","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"memory":{"_input_type":"BoolInput","advanced":true,"display_name":"Memory","dynamic":false,"info":"Whether the agent should have memory or not","list":false,"list_add_label":"Add More","name":"memory","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"previous_task":{"_input_type":"HandleInput","advanced":false,"display_name":"Previous Task","dynamic":false,"info":"The previous task in the sequence (for chaining).","input_types":["SequentialTask"],"list":false,"list_add_label":"Add More","name":"previous_task","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"role":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Role","dynamic":false,"info":"The role of the agent.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"role","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":""},"task_description":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Task Description","dynamic":false,"info":"Descriptive text detailing task's purpose and execution.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"task_description","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":""},"tools":{"_input_type":"HandleInput","advanced":false,"display_name":"Tools","dynamic":false,"info":"Tools at agent's disposal","input_types":["Tool"],"list":true,"list_add_label":"Add More","name":"tools","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":[]},"verbose":{"_input_type":"BoolInput","advanced":true,"display_name":"Verbose","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"verbose","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false},"SequentialTaskComponent":{"base_classes":["SequentialTask"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Each task must have a description, an expected output and an agent responsible for execution.","display_name":"Sequential Task","documentation":"","edited":false,"field_order":["task_description","expected_output","tools","agent","task","async_execution"],"frozen":false,"icon":"CrewAI","legacy":true,"metadata":{"code_hash":"b1f17b8fcc5c","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.crewai.sequential_task.SequentialTaskComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Task","group_outputs":false,"method":"build_task","name":"task_output","selected":"SequentialTask","tool_mode":true,"types":["SequentialTask"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","agent":{"_input_type":"HandleInput","advanced":false,"display_name":"Agent","dynamic":false,"info":"CrewAI Agent that will perform the task","input_types":["Agent"],"list":false,"list_add_label":"Add More","name":"agent","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"async_execution":{"_input_type":"BoolInput","advanced":true,"display_name":"Async Execution","dynamic":false,"info":"Boolean flag indicating asynchronous task execution.","list":false,"list_add_label":"Add More","name":"async_execution","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from lfx.base.agents.crewai.tasks import SequentialTask\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, HandleInput, MultilineInput, Output\n\n\nclass SequentialTaskComponent(Component):\n display_name: str = \"Sequential Task\"\n description: str = \"Each task must have a description, an expected output and an agent responsible for execution.\"\n icon = \"CrewAI\"\n legacy = True\n replacement = \"agents.Agent\"\n inputs = [\n MultilineInput(\n name=\"task_description\",\n display_name=\"Description\",\n info=\"Descriptive text detailing task's purpose and execution.\",\n ),\n MultilineInput(\n name=\"expected_output\",\n display_name=\"Expected Output\",\n info=\"Clear definition of expected task outcome.\",\n ),\n HandleInput(\n name=\"tools\",\n display_name=\"Tools\",\n input_types=[\"Tool\"],\n is_list=True,\n info=\"List of tools/resources limited for task execution. Uses the Agent tools by default.\",\n required=False,\n advanced=True,\n ),\n HandleInput(\n name=\"agent\",\n display_name=\"Agent\",\n input_types=[\"Agent\"],\n info=\"CrewAI Agent that will perform the task\",\n required=True,\n ),\n HandleInput(\n name=\"task\",\n display_name=\"Task\",\n input_types=[\"SequentialTask\"],\n info=\"CrewAI Task that will perform the task\",\n ),\n BoolInput(\n name=\"async_execution\",\n display_name=\"Async Execution\",\n value=True,\n advanced=True,\n info=\"Boolean flag indicating asynchronous task execution.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Task\", name=\"task_output\", method=\"build_task\"),\n ]\n\n def build_task(self) -> list[SequentialTask]:\n tasks: list[SequentialTask] = []\n task = SequentialTask(\n description=self.task_description,\n expected_output=self.expected_output,\n tools=self.agent.tools,\n async_execution=False,\n agent=self.agent,\n )\n tasks.append(task)\n self.status = task\n if self.task:\n if isinstance(self.task, list) and all(isinstance(task_item, SequentialTask) for task_item in self.task):\n tasks = self.task + tasks\n elif isinstance(self.task, SequentialTask):\n tasks = [self.task, *tasks]\n return tasks\n"},"expected_output":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Expected Output","dynamic":false,"info":"Clear definition of expected task outcome.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"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":""},"task":{"_input_type":"HandleInput","advanced":false,"display_name":"Task","dynamic":false,"info":"CrewAI Task that will perform the task","input_types":["SequentialTask"],"list":false,"list_add_label":"Add More","name":"task","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"task_description":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Description","dynamic":false,"info":"Descriptive text detailing task's purpose and execution.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"task_description","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":""},"tools":{"_input_type":"HandleInput","advanced":true,"display_name":"Tools","dynamic":false,"info":"List of tools/resources limited for task execution. Uses the Agent tools by default.","input_types":["Tool"],"list":true,"list_add_label":"Add More","name":"tools","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""}},"tool_mode":false}}],["cuga",{"Cuga":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Define the Cuga agent's instructions, then assign it a task.","display_name":"Cuga","documentation":"https://docs.langflow.org/bundles-cuga","edited":false,"field_order":["agent_llm","max_tokens","model_kwargs","json_mode","model_name","openai_api_base","api_key","temperature","seed","max_retries","timeout","instructions","n_messages","tools","input_value","handle_parsing_errors","verbose","max_iterations","agent_description","add_current_date_tool","lite_mode","lite_mode_tool_threshold","decomposition_strategy","browser_enabled","web_apps"],"frozen":false,"icon":"bot","legacy":false,"metadata":{"code_hash":"35e838f89d13","dependencies":{"dependencies":[{"name":"langchain_core","version":"0.3.80"},{"name":"lfx","version":null},{"name":"cuga","version":"0.2.6"}],"total_dependencies":3},"module":"lfx.components.cuga.cuga_agent.CugaComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Response","group_outputs":false,"method":"message_response","name":"response","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","add_current_date_tool":{"_input_type":"BoolInput","advanced":true,"display_name":"Current Date","dynamic":false,"info":"If true, will add a tool to the agent that returns the current date.","list":false,"list_add_label":"Add More","name":"add_current_date_tool","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"agent_description":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Agent Description [Deprecated]","dynamic":false,"info":"The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"agent_description","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":"A helpful assistant with access to the following tools:"},"agent_llm":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model Provider","dynamic":false,"external_options":{},"info":"The provider of the language model that the agent will use to generate responses.","input_types":[],"name":"agent_llm","options":["OpenAI","Custom"],"options_metadata":[{"icon":"OpenAI"},{"icon":"brain"}],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"OpenAI"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"OpenAI API Key","dynamic":false,"info":"The OpenAI API Key to use for the OpenAI model.","input_types":[],"load_from_db":false,"name":"api_key","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"browser_enabled":{"_input_type":"BoolInput","advanced":true,"display_name":"Enable Browser","dynamic":false,"info":"Toggle to enable a built-in browser tool for web scraping and searching.","list":false,"list_add_label":"Add More","name":"browser_enabled","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"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 asyncio\nimport json\nimport traceback\nimport uuid\nfrom collections.abc import AsyncIterator\nfrom typing import TYPE_CHECKING, Any, cast\n\nfrom langchain_core.agents import AgentFinish\nfrom langchain_core.messages import AIMessage, HumanMessage\nfrom langchain_core.tools import StructuredTool\n\nfrom lfx.base.agents.agent import LCToolsAgentComponent\nfrom lfx.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS,\n MODEL_PROVIDERS_DICT,\n MODELS_METADATA,\n)\nfrom lfx.base.models.model_utils import get_model_name\nfrom lfx.components.helpers import CurrentDateComponent\nfrom lfx.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom lfx.components.models_and_agents.memory import MemoryComponent\nfrom lfx.custom.custom_component.component import _get_component_toolkit\nfrom lfx.custom.utils import update_component_build_config\nfrom lfx.field_typing import Tool\nfrom lfx.io import BoolInput, DropdownInput, IntInput, MultilineInput, Output\nfrom lfx.log.logger import logger\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\n\nif TYPE_CHECKING:\n from lfx.schema.log import SendMessageFunctionType\n\n\ndef set_advanced_true(component_input):\n \"\"\"Set the advanced flag to True for a component input.\n\n Args:\n component_input: The component input to modify\n\n Returns:\n The modified component input with advanced=True\n \"\"\"\n component_input.advanced = True\n return component_input\n\n\nMODEL_PROVIDERS_LIST = [\"OpenAI\"]\n\n\nclass CugaComponent(ToolCallingAgentComponent):\n \"\"\"Cuga Agent Component for advanced AI task execution.\n\n The Cuga component is an advanced AI agent that can execute complex tasks using\n various tools and browser automation. It supports custom instructions, web applications,\n and API interactions.\n\n Attributes:\n display_name: Human-readable name for the component\n description: Brief description of the component's purpose\n documentation: URL to component documentation\n icon: Icon identifier for the UI\n name: Internal component name\n \"\"\"\n\n display_name: str = \"Cuga\"\n description: str = \"Define the Cuga agent's instructions, then assign it a task.\"\n documentation: str = \"https://docs.langflow.org/bundles-cuga\"\n icon = \"bot\"\n name = \"Cuga\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*MODEL_PROVIDERS_LIST, \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in MODEL_PROVIDERS_LIST] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"instructions\",\n display_name=\"Instructions\",\n info=(\n \"Custom instructions for the agent to adhere to during its operation.\\n\"\n \"Example:\\n\"\n \"## Plan\\n\"\n \"< planning instructions e.g. which tools and when to use>\\n\"\n \"## Answer\\n\"\n \"< final answer instructions how to answer>\"\n ),\n value=\"\",\n advanced=False,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Chat History Messages\",\n value=100,\n info=\"Number of chat history messages to retrieve.\",\n advanced=True,\n show=True,\n ),\n *LCToolsAgentComponent.get_base_inputs(),\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n BoolInput(\n name=\"lite_mode\",\n display_name=\"Enable CugaLite\",\n info=\"Faster reasoning for simple tasks. Enable CugaLite for simple API tasks.\",\n value=True,\n advanced=True,\n ),\n IntInput(\n name=\"lite_mode_tool_threshold\",\n display_name=\"CugaLite Tool Threshold\",\n info=\"Route to CugaLite if app has fewer than this many tools.\",\n value=25,\n advanced=True,\n ),\n DropdownInput(\n name=\"decomposition_strategy\",\n display_name=\"Decomposition Strategy\",\n info=\"Strategy for task decomposition: 'flexible' allows multiple subtasks per app,\\n\"\n \" 'exact' enforces one subtask per app.\",\n options=[\"flexible\", \"exact\"],\n value=\"flexible\",\n advanced=True,\n ),\n BoolInput(\n name=\"browser_enabled\",\n display_name=\"Enable Browser\",\n info=\"Toggle to enable a built-in browser tool for web scraping and searching.\",\n value=False,\n advanced=True,\n ),\n MultilineInput(\n name=\"web_apps\",\n display_name=\"Web applications\",\n info=(\n \"Cuga will automatically start this web application when Enable Browser is true. \"\n \"Currently only supports one web application. Example: https://example.com\"\n ),\n value=\"\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(name=\"response\", display_name=\"Response\", method=\"message_response\"),\n ]\n\n async def call_agent(\n self, current_input: str, tools: list[Tool], history_messages: list[Message], llm\n ) -> AsyncIterator[dict[str, Any]]:\n \"\"\"Execute the Cuga agent with the given input and tools.\n\n This method initializes and runs the Cuga agent, processing the input through\n the agent's workflow and yielding events for real-time monitoring.\n\n Args:\n current_input: The user input to process\n tools: List of available tools for the agent\n history_messages: Previous conversation history\n llm: The language model instance to use\n\n Yields:\n dict: Agent events including tool usage, thinking, and final results\n\n Raises:\n ValueError: If there's an error in agent initialization\n TypeError: If there's a type error in processing\n RuntimeError: If there's a runtime error during execution\n ConnectionError: If there's a connection issue\n \"\"\"\n yield {\n \"event\": \"on_chain_start\",\n \"run_id\": str(uuid.uuid4()),\n \"name\": \"CUGA_initializing\",\n \"data\": {\"input\": {\"input\": current_input, \"chat_history\": []}},\n }\n logger.debug(f\"[CUGA] LLM MODEL TYPE: {type(llm)}\")\n if current_input:\n # Import settings first\n from cuga.config import settings\n\n # Use Dynaconf's set() method to update settings dynamically\n # This properly updates the settings object without corruption\n logger.debug(\"[CUGA] Updating CUGA settings via Dynaconf set() method\")\n\n settings.advanced_features.registry = False\n settings.advanced_features.lite_mode = self.lite_mode\n settings.advanced_features.lite_mode_tool_threshold = self.lite_mode_tool_threshold\n settings.advanced_features.decomposition_strategy = self.decomposition_strategy\n\n if self.browser_enabled:\n logger.debug(\"[CUGA] browser_enabled is true, setting mode to hybrid\")\n settings.advanced_features.mode = \"hybrid\"\n settings.advanced_features.use_vision = False\n else:\n logger.debug(\"[CUGA] browser_enabled is false, setting mode to api\")\n settings.advanced_features.mode = \"api\"\n\n from cuga.backend.activity_tracker.tracker import ActivityTracker\n from cuga.backend.cuga_graph.utils.agent_loop import StreamEvent\n from cuga.backend.cuga_graph.utils.controller import (\n AgentRunner as CugaAgent,\n )\n from cuga.backend.cuga_graph.utils.controller import (\n ExperimentResult as AgentResult,\n )\n from cuga.backend.llm.models import LLMManager\n from cuga.configurations.instructions_manager import InstructionsManager\n\n # Reset var_manager if this is the first message in history\n logger.debug(f\"[CUGA] Checking history_messages: count={len(history_messages) if history_messages else 0}\")\n if not history_messages or len(history_messages) == 0:\n logger.debug(\"[CUGA] First message in history detected, resetting var_manager\")\n else:\n logger.debug(f\"[CUGA] Continuing conversation with {len(history_messages)} previous messages\")\n\n llm_manager = LLMManager()\n llm_manager.set_llm(llm)\n instructions_manager = InstructionsManager()\n\n instructions_to_use = self.instructions or \"\"\n logger.debug(f\"[CUGA] instructions are: {instructions_to_use}\")\n instructions_manager.set_instructions_from_one_file(instructions_to_use)\n tracker = ActivityTracker()\n tracker.set_tools(tools)\n thread_id = self.graph.session_id\n logger.debug(f\"[CUGA] Using thread_id (session_id): {thread_id}\")\n cuga_agent = CugaAgent(browser_enabled=self.browser_enabled, thread_id=thread_id)\n if self.browser_enabled:\n await cuga_agent.initialize_freemode_env(start_url=self.web_apps.strip(), interface_mode=\"browser_only\")\n else:\n await cuga_agent.initialize_appworld_env()\n\n yield {\n \"event\": \"on_chain_start\",\n \"run_id\": str(uuid.uuid4()),\n \"name\": \"CUGA_thinking...\",\n \"data\": {\"input\": {\"input\": current_input, \"chat_history\": []}},\n }\n logger.debug(f\"[CUGA] current web apps are {self.web_apps}\")\n logger.debug(f\"[CUGA] Processing input: {current_input}\")\n try:\n # Convert history to LangChain format for the event\n logger.debug(f\"[CUGA] Converting {len(history_messages)} history messages to LangChain format\")\n lc_messages = []\n for i, msg in enumerate(history_messages):\n msg_text = getattr(msg, \"text\", \"N/A\")[:50] if hasattr(msg, \"text\") else \"N/A\"\n logger.debug(\n f\"[CUGA] Message {i}: type={type(msg)}, sender={getattr(msg, 'sender', 'N/A')}, \"\n f\"text={msg_text}...\"\n )\n if hasattr(msg, \"sender\") and msg.sender == \"Human\":\n lc_messages.append(HumanMessage(content=msg.text))\n else:\n lc_messages.append(AIMessage(content=msg.text))\n\n logger.debug(f\"[CUGA] Converted to {len(lc_messages)} LangChain messages\")\n await asyncio.sleep(0.5)\n\n # 2. Build final response\n response_parts = []\n\n response_parts.append(f\"Processed input: '{current_input}'\")\n response_parts.append(f\"Available tools: {len(tools)}\")\n last_event: StreamEvent | None = None\n tool_run_id: str | None = None\n # 3. Chain end event with AgentFinish\n async for event in cuga_agent.run_task_generic_yield(\n eval_mode=False, goal=current_input, chat_messages=lc_messages\n ):\n logger.debug(f\"[CUGA] recieved event {event}\")\n if last_event is not None and tool_run_id is not None:\n logger.debug(f\"[CUGA] last event {last_event}\")\n try:\n # TODO: Extract data\n data_dict = json.loads(last_event.data)\n except json.JSONDecodeError:\n data_dict = last_event.data\n if last_event.name == \"CodeAgent\":\n data_dict = data_dict[\"code\"]\n yield {\n \"event\": \"on_tool_end\",\n \"run_id\": tool_run_id,\n \"name\": last_event.name,\n \"data\": {\"output\": data_dict},\n }\n if isinstance(event, StreamEvent):\n tool_run_id = str(uuid.uuid4())\n last_event = StreamEvent(name=event.name, data=event.data)\n tool_event = {\n \"event\": \"on_tool_start\",\n \"run_id\": tool_run_id,\n \"name\": event.name,\n \"data\": {\"input\": {}},\n }\n logger.debug(f\"[CUGA] Yielding tool_start event: {event.name}\")\n yield tool_event\n\n if isinstance(event, AgentResult):\n task_result = event\n end_event = {\n \"event\": \"on_chain_end\",\n \"run_id\": str(uuid.uuid4()),\n \"name\": \"CugaAgent\",\n \"data\": {\"output\": AgentFinish(return_values={\"output\": task_result.answer}, log=\"\")},\n }\n answer_preview = task_result.answer[:100] if task_result.answer else \"None\"\n logger.info(f\"[CUGA] Yielding chain_end event with answer: {answer_preview}...\")\n yield end_event\n\n except (ValueError, TypeError, RuntimeError, ConnectionError) as e:\n logger.error(f\"[CUGA] An error occurred: {e!s}\")\n logger.error(f\"[CUGA] Traceback: {traceback.format_exc()}\")\n error_msg = f\"CUGA Agent error: {e!s}\"\n logger.error(f\"[CUGA] Error occurred: {error_msg}\")\n\n # Emit error event\n yield {\n \"event\": \"on_chain_error\",\n \"run_id\": str(uuid.uuid4()),\n \"name\": \"CugaAgent\",\n \"data\": {\"error\": error_msg},\n }\n\n async def message_response(self) -> Message:\n \"\"\"Generate a message response using the Cuga agent.\n\n This method processes the input through the Cuga agent and returns a structured\n message response. It handles agent initialization, tool setup, and event processing.\n\n Returns:\n Message: The agent's response message\n\n Raises:\n Exception: If there's an error during agent execution\n \"\"\"\n logger.debug(\"[CUGA] Starting Cuga agent run for message_response.\")\n logger.debug(f\"[CUGA] Agent input value: {self.input_value}\")\n\n # Validate input is not empty\n if not self.input_value or not str(self.input_value).strip():\n msg = \"Message cannot be empty. Please provide a valid message.\"\n raise ValueError(msg)\n\n try:\n from lfx.schema.content_block import ContentBlock\n from lfx.schema.message import MESSAGE_SENDER_AI\n\n llm_model, self.chat_history, self.tools = await self.get_agent_requirements()\n\n # Create agent message for event processing\n agent_message = Message(\n sender=MESSAGE_SENDER_AI,\n sender_name=\"Cuga\",\n properties={\"icon\": \"Bot\", \"state\": \"partial\"},\n content_blocks=[ContentBlock(title=\"Agent Steps\", contents=[])],\n session_id=self.graph.session_id,\n )\n\n # Pre-assign an ID for event processing, following the base agent pattern\n # This ensures streaming works even when not connected to ChatOutput\n if not self.is_connected_to_chat_output():\n # When not connected to ChatOutput, assign ID upfront for streaming support\n agent_message.data[\"id\"] = uuid.uuid4()\n\n # Get input text\n input_text = self.input_value.text if hasattr(self.input_value, \"text\") else str(self.input_value)\n\n # Create event iterator from call_agent\n event_iterator = self.call_agent(\n current_input=input_text, tools=self.tools or [], history_messages=self.chat_history, llm=llm_model\n )\n\n # Process events using the existing event processing system\n from lfx.base.agents.events import process_agent_events\n\n # Create a wrapper that forces DB updates for event handlers\n # This ensures the UI can see loading steps in real-time via polling\n async def force_db_update_send_message(message, id_=None, *, skip_db_update=False): # noqa: ARG001\n # Always persist to DB so polling-based UI shows loading steps in real-time\n content_blocks_len = len(message.content_blocks[0].contents) if message.content_blocks else 0\n logger.debug(\n f\"[CUGA] Sending message update - state: {message.properties.state}, \"\n f\"content_blocks: {content_blocks_len}\"\n )\n\n result = await self.send_message(message, id_=id_, skip_db_update=False)\n\n logger.debug(f\"[CUGA] Message processed with ID: {result.id}\")\n return result\n\n result = await process_agent_events(\n event_iterator, agent_message, cast(\"SendMessageFunctionType\", force_db_update_send_message)\n )\n\n logger.debug(\"[CUGA] Agent run finished successfully.\")\n logger.debug(f\"[CUGA] Agent output: {result}\")\n\n except Exception as e:\n logger.error(f\"[CUGA] Error in message_response: {e}\")\n logger.error(f\"[CUGA] An error occurred: {e!s}\")\n logger.error(f\"[CUGA] Traceback: {traceback.format_exc()}\")\n\n # Check if error is related to Playwright installation\n error_str = str(e).lower()\n if \"playwright install\" in error_str:\n msg = (\n \"Playwright is not installed. Please install Playwright Chromium using: \"\n \"uv run -m playwright install chromium\"\n )\n raise ValueError(msg) from e\n\n raise\n else:\n return result\n\n async def get_agent_requirements(self):\n \"\"\"Get the agent requirements for the Cuga agent.\n\n This method retrieves and configures all necessary components for the agent\n including the language model, chat history, and tools.\n\n Returns:\n tuple: A tuple containing (llm_model, chat_history, tools)\n\n Raises:\n ValueError: If no language model is selected or if there's an error\n in model initialization\n \"\"\"\n llm_model, display_name = await self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n if isinstance(self.chat_history, Message):\n self.chat_history = [self.chat_history]\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list):\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # --- ADDED LOGGING START ---\n logger.debug(\"[CUGA] Retrieved agent requirements: LLM, chat history, and tools.\")\n logger.debug(f\"[CUGA] LLM model: {self.model_name}\")\n logger.debug(f\"[CUGA] Number of chat history messages: {len(self.chat_history)}\")\n logger.debug(f\"[CUGA] Tools available: {[tool.name for tool in self.tools]}\")\n logger.debug(f\"[CUGA] metadata: {[tool.metadata for tool in self.tools]}\")\n # --- ADDED LOGGING END ---\n\n return llm_model, self.chat_history, self.tools\n\n async def get_memory_data(self):\n \"\"\"Retrieve chat history messages.\n\n This method fetches the conversation history from memory, excluding the current\n input message to avoid duplication.\n\n Returns:\n list: List of Message objects representing the chat history\n \"\"\"\n logger.debug(\"[CUGA] Retrieving chat history messages.\")\n logger.debug(f\"[CUGA] Session ID: {self.graph.session_id}\")\n logger.debug(f\"[CUGA] n_messages: {self.n_messages}\")\n logger.debug(f\"[CUGA] input_value: {self.input_value}\")\n logger.debug(f\"[CUGA] input_value type: {type(self.input_value)}\")\n logger.debug(f\"[CUGA] input_value id: {getattr(self.input_value, 'id', None)}\")\n\n messages = (\n await MemoryComponent(**self.get_base_args())\n .set(session_id=str(self.graph.session_id), order=\"Ascending\", n_messages=self.n_messages)\n .retrieve_messages()\n )\n logger.debug(f\"[CUGA] Retrieved {len(messages)} messages from memory\")\n return [\n message for message in messages if getattr(message, \"id\", None) != getattr(self.input_value, \"id\", None)\n ]\n\n async def get_llm(self):\n \"\"\"Get language model for the Cuga agent.\n\n This method initializes and configures the language model based on the\n selected provider and parameters.\n\n Returns:\n tuple: A tuple containing (llm_model, display_name)\n\n Raises:\n ValueError: If the model provider is invalid or model initialization fails\n \"\"\"\n logger.debug(\"[CUGA] Getting language model for the agent.\")\n logger.debug(f\"[CUGA] Requested LLM provider: {self.agent_llm}\")\n\n if not isinstance(self.agent_llm, str):\n logger.debug(\"[CUGA] Agent LLM is already a model instance.\")\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n logger.debug(f\"[CUGA] Successfully built LLM model from provider: {self.agent_llm}\")\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except (AttributeError, ValueError, TypeError, RuntimeError) as e:\n await logger.aerror(f\"[CUGA] Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n \"\"\"Build LLM model with parameters.\n\n This method constructs a language model instance using the provided component\n class and input parameters.\n\n Args:\n component: The LLM component class to instantiate\n inputs: List of input field definitions\n prefix: Optional prefix for parameter names\n\n Returns:\n The configured LLM model instance\n \"\"\"\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n \"\"\"Set component parameters based on provider.\n\n This method configures component parameters according to the selected\n model provider's requirements.\n\n Args:\n component: The component to configure\n\n Returns:\n The configured component\n \"\"\"\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {}\n for input_ in inputs:\n if hasattr(self, f\"{prefix}{input_.name}\"):\n model_kwargs[input_.name] = getattr(self, f\"{prefix}{input_.name}\")\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\n\n This method removes unwanted fields from the build configuration.\n\n Args:\n build_config: The build configuration dictionary\n fields: Fields to remove (can be dict or list of strings)\n \"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\n\n This method ensures all fields in the build configuration have proper\n input types defined.\n\n Args:\n build_config: The build configuration to update\n\n Returns:\n dotdict: Updated build configuration with input types\n \"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n \"\"\"Update build configuration based on field changes.\n\n This method dynamically updates the component's build configuration when\n certain fields change, particularly the model provider selection.\n\n Args:\n build_config: The current build configuration\n field_value: The new value for the field\n field_name: The name of the field being changed\n\n Returns:\n dotdict: Updated build configuration\n\n Raises:\n ValueError: If required keys are missing from the configuration\n \"\"\"\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"instructions\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def _get_tools(self) -> list[Tool]:\n \"\"\"Build agent tools.\n\n This method constructs the list of tools available to the Cuga agent,\n including component tools and any additional configured tools.\n\n Returns:\n list[Tool]: List of available tools for the agent\n \"\"\"\n logger.debug(\"[CUGA] Building agent tools.\")\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=\"Call_CugaAgent\", tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n logger.debug(f\"[CUGA] Tools built: {[tool.name for tool in tools]}\")\n return tools\n"},"decomposition_strategy":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Decomposition Strategy","dynamic":false,"external_options":{},"info":"Strategy for task decomposition: 'flexible' allows multiple subtasks per app,\n 'exact' enforces one subtask per app.","name":"decomposition_strategy","options":["flexible","exact"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"flexible"},"handle_parsing_errors":{"_input_type":"BoolInput","advanced":true,"display_name":"Handle Parse Errors","dynamic":false,"info":"Should the Agent fix errors when reading user input for better processing?","list":false,"list_add_label":"Add More","name":"handle_parsing_errors","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"The input provided by the user for the agent to process.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"instructions":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Instructions","dynamic":false,"info":"Custom instructions for the agent to adhere to during its operation.\nExample:\n## Plan\n< planning instructions e.g. which tools and when to use>\n## Answer\n< final answer instructions how to answer>","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"instructions","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":""},"json_mode":{"_input_type":"BoolInput","advanced":true,"display_name":"JSON Mode","dynamic":false,"info":"If True, it will output JSON regardless of passing a schema.","list":false,"list_add_label":"Add More","name":"json_mode","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"lite_mode":{"_input_type":"BoolInput","advanced":true,"display_name":"Enable CugaLite","dynamic":false,"info":"Faster reasoning for simple tasks. Enable CugaLite for simple API tasks.","list":false,"list_add_label":"Add More","name":"lite_mode","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"lite_mode_tool_threshold":{"_input_type":"IntInput","advanced":true,"display_name":"CugaLite Tool Threshold","dynamic":false,"info":"Route to CugaLite if app has fewer than this many tools.","list":false,"list_add_label":"Add More","name":"lite_mode_tool_threshold","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":25},"max_iterations":{"_input_type":"IntInput","advanced":true,"display_name":"Max Iterations","dynamic":false,"info":"The maximum number of attempts the agent can make to complete its task before it stops.","list":false,"list_add_label":"Add More","name":"max_iterations","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":15},"max_retries":{"_input_type":"IntInput","advanced":true,"display_name":"Max Retries","dynamic":false,"info":"The maximum number of retries to make when generating.","list":false,"list_add_label":"Add More","name":"max_retries","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":5},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Max Tokens","dynamic":false,"info":"The maximum number of tokens to generate. Set to 0 for unlimited tokens.","list":false,"list_add_label":"Add More","name":"max_tokens","override_skip":false,"placeholder":"","range_spec":{"max":128000.0,"min":0.0,"step":0.1,"step_type":"float"},"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int","value":""},"model_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Model Kwargs","dynamic":false,"info":"Additional keyword arguments to pass to the model.","list":false,"list_add_label":"Add More","name":"model_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{},"display_name":"Model Name","dynamic":false,"external_options":{},"info":"To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.","name":"model_name","options":["gpt-4o-mini","gpt-4o","gpt-4-turbo","gpt-4-turbo-preview","gpt-4","gpt-3.5-turbo","gpt-5.1","gpt-5","gpt-5-mini","gpt-5-nano","gpt-5-chat-latest","o1"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":false,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"gpt-4o-mini"},"n_messages":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Chat History Messages","dynamic":false,"info":"Number of chat history messages to retrieve.","list":false,"list_add_label":"Add More","name":"n_messages","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":100},"openai_api_base":{"_input_type":"StrInput","advanced":true,"display_name":"OpenAI API Base","dynamic":false,"info":"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"openai_api_base","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"seed":{"_input_type":"IntInput","advanced":true,"display_name":"Seed","dynamic":false,"info":"The seed controls the reproducibility of the job.","list":false,"list_add_label":"Add More","name":"seed","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":1},"temperature":{"_input_type":"SliderInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"","max_label":"","max_label_icon":"","min_label":"","min_label_icon":"","name":"temperature","override_skip":false,"placeholder":"","range_spec":{"max":1.0,"min":0.0,"step":0.01,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":0.1},"timeout":{"_input_type":"IntInput","advanced":true,"display_name":"Timeout","dynamic":false,"info":"The timeout for requests to OpenAI completion API.","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":700},"tools":{"_input_type":"HandleInput","advanced":false,"display_name":"Tools","dynamic":false,"info":"These are the tools that the agent can use to help with tasks.","input_types":["Tool"],"list":true,"list_add_label":"Add More","name":"tools","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"verbose":{"_input_type":"BoolInput","advanced":true,"display_name":"Verbose","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"verbose","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"web_apps":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Web applications","dynamic":false,"info":"Cuga will automatically start this web application when Enable Browser is true. Currently only supports one web application. Example: https://example.com","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"web_apps","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":""}},"tool_mode":false}}],["custom_component",{"CustomComponent":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Use as a template to create your own component.","display_name":"Custom Component","documentation":"https://docs.langflow.org/components-custom-components","edited":false,"field_order":["input_value"],"frozen":false,"icon":"code","legacy":false,"metadata":{"code_hash":"d50a68a6fa57","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.custom_component.custom_component.CustomComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Output","group_outputs":false,"method":"build_output","name":"output","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"# from lfx.field_typing import Data\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import MessageTextInput, Output\nfrom lfx.schema.data import Data\n\n\nclass CustomComponent(Component):\n display_name = \"Custom Component\"\n description = \"Use as a template to create your own component.\"\n documentation: str = \"https://docs.langflow.org/components-custom-components\"\n icon = \"code\"\n name = \"CustomComponent\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Input Value\",\n info=\"This is a custom component Input\",\n value=\"Hello, World!\",\n tool_mode=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", method=\"build_output\"),\n ]\n\n def build_output(self) -> Data:\n data = Data(value=self.input_value)\n self.status = data\n return data\n"},"input_value":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Input Value","dynamic":false,"info":"This is a custom component Input","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"Hello, World!"}},"tool_mode":false}}],["data_source",{"APIRequest":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Make HTTP requests using URL or cURL commands.","display_name":"API Request","documentation":"https://docs.langflow.org/api-request","edited":false,"field_order":["url_input","curl_input","method","mode","query_params","body","headers","timeout","follow_redirects","save_to_file","include_httpx_metadata"],"frozen":false,"icon":"Globe","legacy":false,"metadata":{"code_hash":"7f013aba27c9","dependencies":{"dependencies":[{"name":"aiofiles","version":"24.1.0"},{"name":"httpx","version":"0.28.1"},{"name":"validators","version":"0.34.0"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.data_source.api_request.APIRequestComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"API Response","group_outputs":false,"method":"make_api_request","name":"data","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","body":{"_input_type":"TableInput","advanced":true,"display_name":"Body","dynamic":false,"info":"The body to send with the request as a dictionary (for POST, PATCH, PUT).","input_types":["Data"],"is_list":true,"list_add_label":"Add More","name":"body","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"table_icon":"Table","table_schema":[{"description":"Parameter name","display_name":"Key","name":"key","type":"str"},{"description":"Parameter value","display_name":"Value","name":"value"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","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 re\nimport tempfile\nfrom datetime import datetime, timezone\nfrom pathlib import Path\nfrom typing import Any\nfrom urllib.parse import parse_qsl, urlencode, urlparse, urlunparse\n\nimport aiofiles\nimport aiofiles.os as aiofiles_os\nimport httpx\nimport validators\n\nfrom lfx.base.curl.parse import parse_context\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import TabInput\nfrom lfx.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n IntInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom lfx.schema.data import Data\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.utils.component_utils import set_current_fields, set_field_advanced, set_field_display\nfrom lfx.utils.ssrf_protection import SSRFProtectionError, validate_url_for_ssrf\n\n# Define fields for each mode\nMODE_FIELDS = {\n \"URL\": [\n \"url_input\",\n \"method\",\n ],\n \"cURL\": [\"curl_input\"],\n}\n\n# Fields that should always be visible\nDEFAULT_FIELDS = [\"mode\"]\n\n\nclass APIRequestComponent(Component):\n display_name = \"API Request\"\n description = \"Make HTTP requests using URL or cURL commands.\"\n documentation: str = \"https://docs.langflow.org/api-request\"\n icon = \"Globe\"\n name = \"APIRequest\"\n\n inputs = [\n MessageTextInput(\n name=\"url_input\",\n display_name=\"URL\",\n info=\"Enter the URL for the request.\",\n advanced=False,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"curl_input\",\n display_name=\"cURL\",\n info=(\n \"Paste a curl command to populate the fields. \"\n \"This will fill in the dictionary fields for headers and body.\"\n ),\n real_time_refresh=True,\n tool_mode=True,\n advanced=True,\n show=False,\n ),\n DropdownInput(\n name=\"method\",\n display_name=\"Method\",\n options=[\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"],\n value=\"GET\",\n info=\"The HTTP method to use.\",\n real_time_refresh=True,\n ),\n TabInput(\n name=\"mode\",\n display_name=\"Mode\",\n options=[\"URL\", \"cURL\"],\n value=\"URL\",\n info=\"Enable cURL mode to populate fields from a cURL command.\",\n real_time_refresh=True,\n ),\n DataInput(\n name=\"query_params\",\n display_name=\"Query Parameters\",\n info=\"The query parameters to append to the URL.\",\n advanced=True,\n ),\n TableInput(\n name=\"body\",\n display_name=\"Body\",\n info=\"The body to send with the request as a dictionary (for POST, PATCH, PUT).\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Parameter name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"description\": \"Parameter value\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n advanced=True,\n real_time_refresh=True,\n ),\n TableInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"The headers to send with the request\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Header\",\n \"type\": \"str\",\n \"description\": \"Header name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Header value\",\n },\n ],\n value=[{\"key\": \"User-Agent\", \"value\": \"Langflow/1.0\"}],\n advanced=True,\n input_types=[\"Data\"],\n real_time_refresh=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n value=30,\n info=\"The timeout to use for the request.\",\n advanced=True,\n ),\n BoolInput(\n name=\"follow_redirects\",\n display_name=\"Follow Redirects\",\n value=False,\n info=(\n \"Whether to follow HTTP redirects. \"\n \"WARNING: Enabling redirects may allow SSRF bypass attacks where a public URL \"\n \"redirects to internal resources. Only enable if you trust the target server. \"\n \"See OWASP SSRF Prevention Cheat Sheet for details.\"\n ),\n advanced=True,\n ),\n BoolInput(\n name=\"save_to_file\",\n display_name=\"Save to File\",\n value=False,\n info=\"Save the API response to a temporary file\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_httpx_metadata\",\n display_name=\"Include HTTPx Metadata\",\n value=False,\n info=(\n \"Include properties such as headers, status_code, response_headers, \"\n \"and redirection_history in the output.\"\n ),\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"API Response\", name=\"data\", method=\"make_api_request\"),\n ]\n\n def _parse_json_value(self, value: Any) -> Any:\n \"\"\"Parse a value that might be a JSON string.\"\"\"\n if not isinstance(value, str):\n return value\n\n try:\n parsed = json.loads(value)\n except json.JSONDecodeError:\n return value\n else:\n return parsed\n\n def _process_body(self, body: Any) -> dict:\n \"\"\"Process the body input into a valid dictionary.\"\"\"\n if body is None:\n return {}\n if hasattr(body, \"data\"):\n body = body.data\n if isinstance(body, dict):\n return self._process_dict_body(body)\n if isinstance(body, str):\n return self._process_string_body(body)\n if isinstance(body, list):\n return self._process_list_body(body)\n return {}\n\n def _process_dict_body(self, body: dict) -> dict:\n \"\"\"Process dictionary body by parsing JSON values.\"\"\"\n return {k: self._parse_json_value(v) for k, v in body.items()}\n\n def _process_string_body(self, body: str) -> dict:\n \"\"\"Process string body by attempting JSON parse.\"\"\"\n try:\n return self._process_body(json.loads(body))\n except json.JSONDecodeError:\n return {\"data\": body}\n\n def _process_list_body(self, body: list) -> dict:\n \"\"\"Process list body by converting to key-value dictionary.\"\"\"\n processed_dict = {}\n try:\n for item in body:\n # Unwrap Data objects\n current_item = item\n if hasattr(item, \"data\"):\n unwrapped_data = item.data\n # If the unwrapped data is a dict but not key-value format, use it directly\n if isinstance(unwrapped_data, dict) and not self._is_valid_key_value_item(unwrapped_data):\n return unwrapped_data\n current_item = unwrapped_data\n if not self._is_valid_key_value_item(current_item):\n continue\n key = current_item[\"key\"]\n value = self._parse_json_value(current_item[\"value\"])\n processed_dict[key] = value\n except (KeyError, TypeError, ValueError) as e:\n self.log(f\"Failed to process body list: {e}\")\n return {}\n return processed_dict\n\n def _is_valid_key_value_item(self, item: Any) -> bool:\n \"\"\"Check if an item is a valid key-value dictionary.\"\"\"\n return isinstance(item, dict) and \"key\" in item and \"value\" in item\n\n def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:\n \"\"\"Parse a cURL command and update build configuration.\"\"\"\n try:\n parsed = parse_context(curl)\n\n # Update basic configuration\n url = parsed.url\n # Normalize URL before setting it\n url = self._normalize_url(url)\n\n build_config[\"url_input\"][\"value\"] = url\n build_config[\"method\"][\"value\"] = parsed.method.upper()\n\n # Process headers\n headers_list = [{\"key\": k, \"value\": v} for k, v in parsed.headers.items()]\n build_config[\"headers\"][\"value\"] = headers_list\n\n # Process body data\n if not parsed.data:\n build_config[\"body\"][\"value\"] = []\n elif parsed.data:\n try:\n json_data = json.loads(parsed.data)\n if isinstance(json_data, dict):\n body_list = [\n {\"key\": k, \"value\": json.dumps(v) if isinstance(v, dict | list) else str(v)}\n for k, v in json_data.items()\n ]\n build_config[\"body\"][\"value\"] = body_list\n else:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": json.dumps(json_data)}]\n except json.JSONDecodeError:\n build_config[\"body\"][\"value\"] = [{\"key\": \"data\", \"value\": parsed.data}]\n\n except Exception as exc:\n msg = f\"Error parsing curl: {exc}\"\n self.log(msg)\n raise ValueError(msg) from exc\n\n return build_config\n\n def _normalize_url(self, url: str) -> str:\n \"\"\"Normalize URL by adding https:// if no protocol is specified.\"\"\"\n if not url or not isinstance(url, str):\n msg = \"URL cannot be empty\"\n raise ValueError(msg)\n\n url = url.strip()\n if url.startswith((\"http://\", \"https://\")):\n return url\n return f\"https://{url}\"\n\n async def make_request(\n self,\n client: httpx.AsyncClient,\n method: str,\n url: str,\n headers: dict | None = None,\n body: Any = None,\n timeout: int = 5,\n *,\n follow_redirects: bool = True,\n save_to_file: bool = False,\n include_httpx_metadata: bool = False,\n ) -> Data:\n method = method.upper()\n if method not in {\"GET\", \"POST\", \"PATCH\", \"PUT\", \"DELETE\"}:\n msg = f\"Unsupported method: {method}\"\n raise ValueError(msg)\n\n processed_body = self._process_body(body)\n redirection_history = []\n\n try:\n # Prepare request parameters\n request_params = {\n \"method\": method,\n \"url\": url,\n \"headers\": headers,\n \"json\": processed_body,\n \"timeout\": timeout,\n \"follow_redirects\": follow_redirects,\n }\n response = await client.request(**request_params)\n\n redirection_history = [\n {\n \"url\": redirect.headers.get(\"Location\", str(redirect.url)),\n \"status_code\": redirect.status_code,\n }\n for redirect in response.history\n ]\n\n is_binary, file_path = await self._response_info(response, with_file_path=save_to_file)\n response_headers = self._headers_to_dict(response.headers)\n\n # Base metadata\n metadata = {\n \"source\": url,\n \"status_code\": response.status_code,\n \"response_headers\": response_headers,\n }\n\n if redirection_history:\n metadata[\"redirection_history\"] = redirection_history\n\n if save_to_file:\n mode = \"wb\" if is_binary else \"w\"\n encoding = response.encoding if mode == \"w\" else None\n if file_path:\n await aiofiles_os.makedirs(file_path.parent, exist_ok=True)\n if is_binary:\n async with aiofiles.open(file_path, \"wb\") as f:\n await f.write(response.content)\n await f.flush()\n else:\n async with aiofiles.open(file_path, \"w\", encoding=encoding) as f:\n await f.write(response.text)\n await f.flush()\n metadata[\"file_path\"] = str(file_path)\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n return Data(data=metadata)\n\n # Handle response content\n if is_binary:\n result = response.content\n else:\n try:\n result = response.json()\n except json.JSONDecodeError:\n self.log(\"Failed to decode JSON response\")\n result = response.text.encode(\"utf-8\")\n\n metadata[\"result\"] = result\n\n if include_httpx_metadata:\n metadata.update({\"headers\": headers})\n\n return Data(data=metadata)\n except (httpx.HTTPError, httpx.RequestError, httpx.TimeoutException) as exc:\n self.log(f\"Error making request to {url}\")\n return Data(\n data={\n \"source\": url,\n \"headers\": headers,\n \"status_code\": 500,\n \"error\": str(exc),\n **({\"redirection_history\": redirection_history} if redirection_history else {}),\n },\n )\n\n def add_query_params(self, url: str, params: dict) -> str:\n \"\"\"Add query parameters to URL efficiently.\"\"\"\n if not params:\n return url\n url_parts = list(urlparse(url))\n query = dict(parse_qsl(url_parts[4]))\n query.update(params)\n url_parts[4] = urlencode(query)\n return urlunparse(url_parts)\n\n def _headers_to_dict(self, headers: httpx.Headers) -> dict[str, str]:\n \"\"\"Convert HTTP headers to a dictionary with lowercased keys.\"\"\"\n return {k.lower(): v for k, v in headers.items()}\n\n def _process_headers(self, headers: Any) -> dict:\n \"\"\"Process the headers input into a valid dictionary.\"\"\"\n if headers is None:\n return {}\n if isinstance(headers, dict):\n return headers\n if isinstance(headers, list):\n return {item[\"key\"]: item[\"value\"] for item in headers if self._is_valid_key_value_item(item)}\n return {}\n\n async def make_api_request(self) -> Data:\n \"\"\"Make HTTP request with optimized parameter handling.\"\"\"\n method = self.method\n url = self.url_input.strip() if isinstance(self.url_input, str) else \"\"\n headers = self.headers or {}\n body = self.body or {}\n timeout = self.timeout\n follow_redirects = self.follow_redirects\n save_to_file = self.save_to_file\n include_httpx_metadata = self.include_httpx_metadata\n\n # Security warning when redirects are enabled\n if follow_redirects:\n self.log(\n \"Security Warning: HTTP redirects are enabled. This may allow SSRF bypass attacks \"\n \"where a public URL redirects to internal resources (e.g., cloud metadata endpoints). \"\n \"Only enable this if you trust the target server.\"\n )\n\n # if self.mode == \"cURL\" and self.curl_input:\n # self._build_config = self.parse_curl(self.curl_input, dotdict())\n # # After parsing curl, get the normalized URL\n # url = self._build_config[\"url_input\"][\"value\"]\n\n # Normalize URL before validation\n url = self._normalize_url(url)\n\n # Validate URL\n if not validators.url(url):\n msg = f\"Invalid URL provided: {url}\"\n raise ValueError(msg)\n\n # SSRF Protection: Validate URL to prevent access to internal resources\n # TODO: In next major version (2.0), remove warn_only=True to enforce blocking\n try:\n validate_url_for_ssrf(url, warn_only=True)\n except SSRFProtectionError as e:\n # This will only raise if SSRF protection is enabled and warn_only=False\n msg = f\"SSRF Protection: {e}\"\n raise ValueError(msg) from e\n\n # Process query parameters\n if isinstance(self.query_params, str):\n query_params = dict(parse_qsl(self.query_params))\n else:\n query_params = self.query_params.data if self.query_params else {}\n\n # Process headers and body\n headers = self._process_headers(headers)\n body = self._process_body(body)\n url = self.add_query_params(url, query_params)\n\n async with httpx.AsyncClient() as client:\n result = await self.make_request(\n client,\n method,\n url,\n headers,\n body,\n timeout,\n follow_redirects=follow_redirects,\n save_to_file=save_to_file,\n include_httpx_metadata=include_httpx_metadata,\n )\n self.status = result\n return result\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:\n \"\"\"Update the build config based on the selected mode.\"\"\"\n if field_name != \"mode\":\n if field_name == \"curl_input\" and self.mode == \"cURL\" and self.curl_input:\n return self.parse_curl(self.curl_input, build_config)\n return build_config\n\n if field_value == \"cURL\":\n set_field_display(build_config, \"curl_input\", value=True)\n if build_config[\"curl_input\"][\"value\"]:\n try:\n build_config = self.parse_curl(build_config[\"curl_input\"][\"value\"], build_config)\n except ValueError as e:\n self.log(f\"Failed to parse cURL input: {e}\")\n else:\n set_field_display(build_config, \"curl_input\", value=False)\n\n return set_current_fields(\n build_config=build_config,\n action_fields=MODE_FIELDS,\n selected_action=field_value,\n default_fields=DEFAULT_FIELDS,\n func=set_field_advanced,\n default_value=True,\n )\n\n async def _response_info(\n self, response: httpx.Response, *, with_file_path: bool = False\n ) -> tuple[bool, Path | None]:\n \"\"\"Determine the file path and whether the response content is binary.\n\n Args:\n response (Response): The HTTP response object.\n with_file_path (bool): Whether to save the response content to a file.\n\n Returns:\n Tuple[bool, Path | None]:\n A tuple containing a boolean indicating if the content is binary and the full file path (if applicable).\n \"\"\"\n content_type = response.headers.get(\"Content-Type\", \"\")\n is_binary = \"application/octet-stream\" in content_type or \"application/binary\" in content_type\n\n if not with_file_path:\n return is_binary, None\n\n component_temp_dir = Path(tempfile.gettempdir()) / self.__class__.__name__\n\n # Create directory asynchronously\n await aiofiles_os.makedirs(component_temp_dir, exist_ok=True)\n\n filename = None\n if \"Content-Disposition\" in response.headers:\n content_disposition = response.headers[\"Content-Disposition\"]\n filename_match = re.search(r'filename=\"(.+?)\"', content_disposition)\n if filename_match:\n extracted_filename = filename_match.group(1)\n filename = extracted_filename\n\n # Step 3: Infer file extension or use part of the request URL if no filename\n if not filename:\n # Extract the last segment of the URL path\n url_path = urlparse(str(response.request.url) if response.request else \"\").path\n base_name = Path(url_path).name # Get the last segment of the path\n if not base_name: # If the path ends with a slash or is empty\n base_name = \"response\"\n\n # Infer file extension\n content_type_to_extension = {\n \"text/plain\": \".txt\",\n \"application/json\": \".json\",\n \"image/jpeg\": \".jpg\",\n \"image/png\": \".png\",\n \"application/octet-stream\": \".bin\",\n }\n extension = content_type_to_extension.get(content_type, \".bin\" if is_binary else \".txt\")\n filename = f\"{base_name}{extension}\"\n\n # Step 4: Define the full file path\n file_path = component_temp_dir / filename\n\n # Step 5: Check if file exists asynchronously and handle accordingly\n try:\n # Try to create the file exclusively (x mode) to check existence\n async with aiofiles.open(file_path, \"x\") as _:\n pass # File created successfully, we can use this path\n except FileExistsError:\n # If file exists, append a timestamp to the filename\n timestamp = datetime.now(timezone.utc).strftime(\"%Y%m%d%H%M%S%f\")\n file_path = component_temp_dir / f\"{timestamp}-{filename}\"\n\n return is_binary, file_path\n"},"curl_input":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"cURL","dynamic":false,"info":"Paste a curl command to populate the fields. This will fill in the dictionary fields for headers and body.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"curl_input","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":false,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"follow_redirects":{"_input_type":"BoolInput","advanced":true,"display_name":"Follow Redirects","dynamic":false,"info":"Whether to follow HTTP redirects. WARNING: Enabling redirects may allow SSRF bypass attacks where a public URL redirects to internal resources. Only enable if you trust the target server. See OWASP SSRF Prevention Cheat Sheet for details.","list":false,"list_add_label":"Add More","name":"follow_redirects","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"headers":{"_input_type":"TableInput","advanced":true,"display_name":"Headers","dynamic":false,"info":"The headers to send with the request","input_types":["Data"],"is_list":true,"list_add_label":"Add More","name":"headers","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"table_icon":"Table","table_schema":[{"description":"Header name","display_name":"Header","name":"key","type":"str"},{"description":"Header value","display_name":"Value","name":"value","type":"str"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","value":[{"key":"User-Agent","value":"Langflow/1.0"}]},"include_httpx_metadata":{"_input_type":"BoolInput","advanced":true,"display_name":"Include HTTPx Metadata","dynamic":false,"info":"Include properties such as headers, status_code, response_headers, and redirection_history in the output.","list":false,"list_add_label":"Add More","name":"include_httpx_metadata","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"method":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Method","dynamic":false,"external_options":{},"info":"The HTTP method to use.","name":"method","options":["GET","POST","PATCH","PUT","DELETE"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"GET"},"mode":{"_input_type":"TabInput","advanced":false,"display_name":"Mode","dynamic":false,"info":"Enable cURL mode to populate fields from a cURL command.","name":"mode","options":["URL","cURL"],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"tab","value":"URL"},"query_params":{"_input_type":"DataInput","advanced":true,"display_name":"Query Parameters","dynamic":false,"info":"The query parameters to append to the URL.","input_types":["Data"],"list":false,"list_add_label":"Add More","name":"query_params","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":"other","value":""},"save_to_file":{"_input_type":"BoolInput","advanced":true,"display_name":"Save to File","dynamic":false,"info":"Save the API response to a temporary file","list":false,"list_add_label":"Add More","name":"save_to_file","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"timeout":{"_input_type":"IntInput","advanced":true,"display_name":"Timeout","dynamic":false,"info":"The timeout to use for the request.","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},"url_input":{"_input_type":"MessageTextInput","advanced":false,"display_name":"URL","dynamic":false,"info":"Enter the URL for the request.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"url_input","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"CSVtoData":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Load a CSV file, CSV from a file path, or a valid CSV string and convert it to a list of Data","display_name":"Load CSV","documentation":"","edited":false,"field_order":["csv_file","csv_path","csv_string","text_key"],"frozen":false,"icon":"file-spreadsheet","legacy":true,"metadata":{"code_hash":"85c7d6df7473","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.data_source.csv_to_data.CSVToDataComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data List","group_outputs":false,"method":"load_csv_to_data","name":"data_list","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":["data.File"],"template":{"_type":"Component","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 csv\nimport io\nfrom pathlib import Path\n\nfrom lfx.base.data.storage_utils import read_file_text\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import FileInput, MessageTextInput, MultilineInput, Output\nfrom lfx.schema.data import Data\nfrom lfx.utils.async_helpers import run_until_complete\n\n\nclass CSVToDataComponent(Component):\n display_name = \"Load CSV\"\n description = \"Load a CSV file, CSV from a file path, or a valid CSV string and convert it to a list of Data\"\n icon = \"file-spreadsheet\"\n name = \"CSVtoData\"\n legacy = True\n replacement = [\"data.File\"]\n\n inputs = [\n FileInput(\n name=\"csv_file\",\n display_name=\"CSV File\",\n file_types=[\"csv\"],\n info=\"Upload a CSV file to convert to a list of Data objects\",\n ),\n MessageTextInput(\n name=\"csv_path\",\n display_name=\"CSV File Path\",\n info=\"Provide the path to the CSV file as pure text\",\n ),\n MultilineInput(\n name=\"csv_string\",\n display_name=\"CSV String\",\n info=\"Paste a CSV string directly to convert to a list of Data objects\",\n ),\n MessageTextInput(\n name=\"text_key\",\n display_name=\"Text Key\",\n info=\"The key to use for the text column. Defaults to 'text'.\",\n value=\"text\",\n ),\n ]\n\n outputs = [\n Output(name=\"data_list\", display_name=\"Data List\", method=\"load_csv_to_data\"),\n ]\n\n def load_csv_to_data(self) -> list[Data]:\n if sum(bool(field) for field in [self.csv_file, self.csv_path, self.csv_string]) != 1:\n msg = \"Please provide exactly one of: CSV file, file path, or CSV string.\"\n raise ValueError(msg)\n\n csv_data = None\n try:\n if self.csv_file:\n # FileInput always provides a local file path\n file_path = self.csv_file\n if not file_path.lower().endswith(\".csv\"):\n self.status = \"The provided file must be a CSV file.\"\n else:\n # Resolve to absolute path and read from local filesystem\n resolved_path = self.resolve_path(file_path)\n csv_bytes = Path(resolved_path).read_bytes()\n csv_data = csv_bytes.decode(\"utf-8\")\n\n elif self.csv_path:\n file_path = self.csv_path\n if not file_path.lower().endswith(\".csv\"):\n self.status = \"The provided path must be to a CSV file.\"\n else:\n csv_data = run_until_complete(\n read_file_text(file_path, encoding=\"utf-8\", resolve_path=self.resolve_path, newline=\"\")\n )\n\n else:\n csv_data = self.csv_string\n\n if csv_data:\n csv_reader = csv.DictReader(io.StringIO(csv_data))\n result = [Data(data=row, text_key=self.text_key) for row in csv_reader]\n\n if not result:\n self.status = \"The CSV data is empty.\"\n return []\n\n self.status = result\n return result\n\n except csv.Error as e:\n error_message = f\"CSV parsing error: {e}\"\n self.status = error_message\n raise ValueError(error_message) from e\n\n except Exception as e:\n error_message = f\"An error occurred: {e}\"\n self.status = error_message\n raise ValueError(error_message) from e\n\n # An error occurred\n raise ValueError(self.status)\n"},"csv_file":{"_input_type":"FileInput","advanced":false,"display_name":"CSV File","dynamic":false,"fileTypes":["csv"],"file_path":"","info":"Upload a CSV file to convert to a list of Data objects","list":false,"list_add_label":"Add More","name":"csv_file","override_skip":false,"placeholder":"","required":false,"show":true,"temp_file":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"file","value":""},"csv_path":{"_input_type":"MessageTextInput","advanced":false,"display_name":"CSV File Path","dynamic":false,"info":"Provide the path to the CSV file as pure text","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"csv_path","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":""},"csv_string":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"CSV String","dynamic":false,"info":"Paste a CSV string directly to convert to a list of Data objects","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"csv_string","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":""},"text_key":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Text Key","dynamic":false,"info":"The key to use for the text column. Defaults to 'text'.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"text_key","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":"text"}},"tool_mode":false},"JSONtoData":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Convert a JSON file, JSON from a file path, or a JSON string to a Data object or a list of Data objects","display_name":"Load JSON","documentation":"","edited":false,"field_order":["json_file","json_path","json_string"],"frozen":false,"icon":"braces","legacy":true,"metadata":{"code_hash":"0d9d78d496a2","dependencies":{"dependencies":[{"name":"json_repair","version":"0.30.3"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.data_source.json_to_data.JSONToDataComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"convert_json_to_data","name":"data","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":["data.File"],"template":{"_type":"Component","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\nfrom pathlib import Path\n\nfrom json_repair import repair_json\n\nfrom lfx.base.data.storage_utils import read_file_text\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import FileInput, MessageTextInput, MultilineInput, Output\nfrom lfx.schema.data import Data\nfrom lfx.utils.async_helpers import run_until_complete\n\n\nclass JSONToDataComponent(Component):\n display_name = \"Load JSON\"\n description = (\n \"Convert a JSON file, JSON from a file path, or a JSON string to a Data object or a list of Data objects\"\n )\n icon = \"braces\"\n name = \"JSONtoData\"\n legacy = True\n replacement = [\"data.File\"]\n\n inputs = [\n FileInput(\n name=\"json_file\",\n display_name=\"JSON File\",\n file_types=[\"json\"],\n info=\"Upload a JSON file to convert to a Data object or list of Data objects\",\n ),\n MessageTextInput(\n name=\"json_path\",\n display_name=\"JSON File Path\",\n info=\"Provide the path to the JSON file as pure text\",\n ),\n MultilineInput(\n name=\"json_string\",\n display_name=\"JSON String\",\n info=\"Enter a valid JSON string (object or array) to convert to a Data object or list of Data objects\",\n ),\n ]\n\n outputs = [\n Output(name=\"data\", display_name=\"Data\", method=\"convert_json_to_data\"),\n ]\n\n def convert_json_to_data(self) -> Data | list[Data]:\n if sum(bool(field) for field in [self.json_file, self.json_path, self.json_string]) != 1:\n msg = \"Please provide exactly one of: JSON file, file path, or JSON string.\"\n self.status = msg\n raise ValueError(msg)\n\n json_data = None\n\n try:\n if self.json_file:\n # FileInput always provides a local file path\n file_path = self.json_file\n if not file_path.lower().endswith(\".json\"):\n self.status = \"The provided file must be a JSON file.\"\n else:\n # Resolve to absolute path and read from local filesystem\n resolved_path = self.resolve_path(file_path)\n json_data = Path(resolved_path).read_text(encoding=\"utf-8\")\n\n elif self.json_path:\n # User-provided text path - could be local or S3 key\n file_path = self.json_path\n if not file_path.lower().endswith(\".json\"):\n self.status = \"The provided path must be to a JSON file.\"\n else:\n json_data = run_until_complete(\n read_file_text(file_path, encoding=\"utf-8\", resolve_path=self.resolve_path)\n )\n\n else:\n json_data = self.json_string\n\n if json_data:\n # Try to parse the JSON string\n try:\n parsed_data = json.loads(json_data)\n except json.JSONDecodeError:\n # If JSON parsing fails, try to repair the JSON string\n repaired_json_string = repair_json(json_data)\n parsed_data = json.loads(repaired_json_string)\n\n # Check if the parsed data is a list\n if isinstance(parsed_data, list):\n result = [Data(data=item) for item in parsed_data]\n else:\n result = Data(data=parsed_data)\n self.status = result\n return result\n\n except (json.JSONDecodeError, SyntaxError, ValueError) as e:\n error_message = f\"Invalid JSON or Python literal: {e}\"\n self.status = error_message\n raise ValueError(error_message) from e\n\n except Exception as e:\n error_message = f\"An error occurred: {e}\"\n self.status = error_message\n raise ValueError(error_message) from e\n\n # An error occurred\n raise ValueError(self.status)\n"},"json_file":{"_input_type":"FileInput","advanced":false,"display_name":"JSON File","dynamic":false,"fileTypes":["json"],"file_path":"","info":"Upload a JSON file to convert to a Data object or list of Data objects","list":false,"list_add_label":"Add More","name":"json_file","override_skip":false,"placeholder":"","required":false,"show":true,"temp_file":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"file","value":""},"json_path":{"_input_type":"MessageTextInput","advanced":false,"display_name":"JSON File Path","dynamic":false,"info":"Provide the path to the JSON file as pure text","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"json_path","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":""},"json_string":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"JSON String","dynamic":false,"info":"Enter a valid JSON string (object or array) to convert to a Data object or list of Data objects","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"json_string","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":""}},"tool_mode":false},"MockDataGenerator":{"base_classes":["Data","DataFrame","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate mock data for testing and development.","display_name":"Mock Data","documentation":"","edited":false,"field_order":[],"frozen":false,"icon":"database","legacy":false,"metadata":{"code_hash":"d21dce7b329b","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"pandas","version":"2.2.3"}],"total_dependencies":2},"module":"lfx.components.data_source.mock_data.MockDataGeneratorComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Result","group_outputs":false,"method":"generate_dataframe_output","name":"dataframe_output","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Result","group_outputs":false,"method":"generate_message_output","name":"message_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Result","group_outputs":false,"method":"generate_data_output","name":"data_output","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 secrets\nfrom datetime import datetime, timedelta, timezone\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import Output\nfrom lfx.schema import Data, DataFrame\nfrom lfx.schema.message import Message\n\n\nclass MockDataGeneratorComponent(Component):\n \"\"\"Mock Data Generator Component.\n\n Generates sample data for testing and development purposes. Supports three main\n Langflow output types: Message (text), Data (JSON), and DataFrame (tabular data).\n\n This component is useful for:\n - Testing workflows without real data sources\n - Prototyping data processing pipelines\n - Creating sample data for demonstrations\n - Development and debugging of Langflow components\n \"\"\"\n\n display_name = \"Mock Data\"\n description = \"Generate mock data for testing and development.\"\n icon = \"database\"\n name = \"MockDataGenerator\"\n\n inputs = []\n\n outputs = [\n Output(display_name=\"Result\", name=\"dataframe_output\", method=\"generate_dataframe_output\"),\n Output(display_name=\"Result\", name=\"message_output\", method=\"generate_message_output\"),\n Output(display_name=\"Result\", name=\"data_output\", method=\"generate_data_output\"),\n ]\n\n def build(self) -> DataFrame:\n \"\"\"Default build method - returns DataFrame when component is standalone.\"\"\"\n return self.generate_dataframe_output()\n\n def generate_message_output(self) -> Message:\n \"\"\"Generate Message output specifically.\n\n Returns:\n Message: A Message object containing Lorem Ipsum text\n \"\"\"\n try:\n self.log(\"Generating Message mock data\")\n message = self._generate_message()\n self.status = f\"Generated Lorem Ipsum message ({len(message.text)} characters)\"\n except (ValueError, TypeError) as e:\n error_msg = f\"Error generating Message data: {e!s}\"\n self.log(error_msg)\n self.status = f\"Error: {error_msg}\"\n return Message(text=f\"Error: {error_msg}\")\n else:\n return message\n\n def generate_data_output(self) -> Data:\n \"\"\"Generate Data output specifically.\n\n Returns:\n Data: A Data object containing sample JSON data (1 record)\n \"\"\"\n try:\n record_count = 1 # Fixed to 1 record for Data output\n self.log(f\"Generating Data mock data with {record_count} record\")\n data = self._generate_data(record_count)\n self.status = f\"Generated JSON data with {len(data.data.get('records', []))} record(s)\"\n except (ValueError, TypeError) as e:\n error_msg = f\"Error generating Data: {e!s}\"\n self.log(error_msg)\n self.status = f\"Error: {error_msg}\"\n return Data(data={\"error\": error_msg, \"success\": False})\n else:\n return data\n\n def generate_dataframe_output(self) -> DataFrame:\n \"\"\"Generate DataFrame output specifically.\n\n Returns:\n DataFrame: A Langflow DataFrame with sample data (50 records)\n \"\"\"\n try:\n record_count = 50 # Fixed to 50 records for DataFrame output\n self.log(f\"Generating DataFrame mock data with {record_count} records\")\n return self._generate_dataframe(record_count)\n except (ValueError, TypeError) as e:\n error_msg = f\"Error generating DataFrame: {e!s}\"\n self.log(error_msg)\n\n try:\n import pandas as pd\n\n error_df = pd.DataFrame({\"error\": [error_msg]})\n return DataFrame(error_df)\n except ImportError:\n # Even without pandas, return DataFrame wrapper\n return DataFrame({\"error\": [error_msg]})\n\n def _generate_message(self) -> Message:\n \"\"\"Generate a sample Message with Lorem Ipsum text.\n\n Returns:\n Message: A Message object containing Lorem Ipsum text\n \"\"\"\n lorem_ipsum_texts = [\n (\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor \"\n \"incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \"\n \"exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\"\n ),\n (\n \"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla \"\n \"pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n \"mollit anim id est laborum.\"\n ),\n (\n \"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, \"\n \"totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto \"\n \"beatae vitae dicta sunt explicabo.\"\n ),\n (\n \"Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, \"\n \"sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.\"\n ),\n (\n \"Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, \"\n \"adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore \"\n \"magnam aliquam quaerat voluptatem.\"\n ),\n ]\n\n selected_text = secrets.choice(lorem_ipsum_texts)\n return Message(text=selected_text)\n\n def _generate_data(self, record_count: int) -> Data:\n \"\"\"Generate sample Data with JSON structure.\n\n Args:\n record_count: Number of records to generate\n\n Returns:\n Data: A Data object containing sample JSON data\n \"\"\"\n # Sample data categories\n companies = [\n \"TechCorp\",\n \"DataSystems\",\n \"CloudWorks\",\n \"InnovateLab\",\n \"DigitalFlow\",\n \"SmartSolutions\",\n \"FutureTech\",\n \"NextGen\",\n ]\n departments = [\"Engineering\", \"Sales\", \"Marketing\", \"HR\", \"Finance\", \"Operations\", \"Support\", \"Research\"]\n statuses = [\"active\", \"pending\", \"completed\", \"cancelled\", \"in_progress\"]\n categories = [\"A\", \"B\", \"C\", \"D\"]\n\n # Generate sample records\n records = []\n base_date = datetime.now(tz=timezone.utc) - timedelta(days=365)\n\n for i in range(record_count):\n record = {\n \"id\": f\"REC-{1000 + i}\",\n \"name\": f\"Sample Record {i + 1}\",\n \"company\": secrets.choice(companies),\n \"department\": secrets.choice(departments),\n \"status\": secrets.choice(statuses),\n \"category\": secrets.choice(categories),\n \"value\": round(secrets.randbelow(9901) + 100 + secrets.randbelow(100) / 100, 2),\n \"quantity\": secrets.randbelow(100) + 1,\n \"rating\": round(secrets.randbelow(41) / 10 + 1, 1),\n \"is_active\": secrets.choice([True, False]),\n \"created_date\": (base_date + timedelta(days=secrets.randbelow(366))).isoformat(),\n \"tags\": [\n secrets.choice(\n [\n \"important\",\n \"urgent\",\n \"review\",\n \"approved\",\n \"draft\",\n \"final\",\n ]\n )\n for _ in range(secrets.randbelow(3) + 1)\n ],\n }\n records.append(record)\n\n # Create the main data structure\n data_structure = {\n \"records\": records,\n \"summary\": {\n \"total_count\": record_count,\n \"active_count\": sum(1 for r in records if r[\"is_active\"]),\n \"total_value\": sum(r[\"value\"] for r in records),\n \"average_rating\": round(sum(r[\"rating\"] for r in records) / record_count, 2),\n \"categories\": list({r[\"category\"] for r in records}),\n \"companies\": list({r[\"company\"] for r in records}),\n },\n }\n\n return Data(data=data_structure)\n\n def _generate_dataframe(self, record_count: int) -> DataFrame:\n \"\"\"Generate sample DataFrame with realistic business data.\n\n Args:\n record_count: Number of rows to generate\n\n Returns:\n DataFrame: A Langflow DataFrame with sample data\n \"\"\"\n try:\n import pandas as pd\n\n self.log(f\"pandas imported successfully, version: {pd.__version__}\")\n except ImportError as e:\n self.log(f\"pandas not available: {e!s}, creating simple DataFrame fallback\")\n # Create a simple DataFrame-like structure without pandas\n data_result = self._generate_data(record_count)\n # Convert Data to simple DataFrame format\n try:\n # Create a basic DataFrame structure from the Data\n records = data_result.data.get(\"records\", [])\n if records:\n # Use first record to get column names\n columns = list(records[0].keys()) if records else [\"error\"]\n rows = [list(record.values()) for record in records]\n else:\n columns = [\"error\"]\n rows = [[\"pandas not available\"]]\n\n # Create a simple dict-based DataFrame representation\n simple_df_data = {\n col: [row[i] if i < len(row) else None for row in rows] for i, col in enumerate(columns)\n }\n\n # Return as DataFrame wrapper (Langflow will handle the display)\n return DataFrame(simple_df_data)\n except (ValueError, TypeError):\n # Ultimate fallback - return the Data as DataFrame\n return DataFrame({\"data\": [str(data_result.data)]})\n\n try:\n self.log(f\"Starting DataFrame generation with {record_count} records\")\n\n # Sample data for realistic business dataset\n first_names = [\n \"John\",\n \"Jane\",\n \"Michael\",\n \"Sarah\",\n \"David\",\n \"Emily\",\n \"Robert\",\n \"Lisa\",\n \"William\",\n \"Jennifer\",\n ]\n last_names = [\n \"Smith\",\n \"Johnson\",\n \"Williams\",\n \"Brown\",\n \"Jones\",\n \"Garcia\",\n \"Miller\",\n \"Davis\",\n \"Rodriguez\",\n \"Martinez\",\n ]\n cities = [\n \"New York\",\n \"Los Angeles\",\n \"Chicago\",\n \"Houston\",\n \"Phoenix\",\n \"Philadelphia\",\n \"San Antonio\",\n \"San Diego\",\n \"Dallas\",\n \"San Jose\",\n ]\n countries = [\"USA\", \"Canada\", \"UK\", \"Germany\", \"France\", \"Australia\", \"Japan\", \"Brazil\", \"India\", \"Mexico\"]\n products = [\n \"Product A\",\n \"Product B\",\n \"Product C\",\n \"Product D\",\n \"Product E\",\n \"Service X\",\n \"Service Y\",\n \"Service Z\",\n ]\n\n # Generate DataFrame data\n data = []\n base_date = datetime.now(tz=timezone.utc) - timedelta(days=365)\n\n self.log(\"Generating row data...\")\n for i in range(record_count):\n row = {\n \"customer_id\": f\"CUST-{10000 + i}\",\n \"first_name\": secrets.choice(first_names),\n \"last_name\": secrets.choice(last_names),\n \"email\": f\"user{i + 1}@example.com\",\n \"age\": secrets.randbelow(63) + 18,\n \"city\": secrets.choice(cities),\n \"country\": secrets.choice(countries),\n \"product\": secrets.choice(products),\n \"order_date\": (base_date + timedelta(days=secrets.randbelow(366))).strftime(\"%Y-%m-%d\"),\n \"order_value\": round(secrets.randbelow(991) + 10 + secrets.randbelow(100) / 100, 2),\n \"quantity\": secrets.randbelow(10) + 1,\n \"discount\": round(secrets.randbelow(31) / 100, 2),\n \"is_premium\": secrets.choice([True, False]),\n \"satisfaction_score\": secrets.randbelow(10) + 1,\n \"last_contact\": (base_date + timedelta(days=secrets.randbelow(366))).strftime(\"%Y-%m-%d\"),\n }\n data.append(row)\n # Create DataFrame\n self.log(\"Creating pandas DataFrame...\")\n df = pd.DataFrame(data)\n self.log(f\"DataFrame created with shape: {df.shape}\")\n\n # Add calculated columns\n self.log(\"Adding calculated columns...\")\n df[\"full_name\"] = df[\"first_name\"] + \" \" + df[\"last_name\"]\n df[\"discounted_value\"] = df[\"order_value\"] * (1 - df[\"discount\"])\n df[\"total_value\"] = df[\"discounted_value\"] * df[\"quantity\"]\n\n # Age group boundaries as constants\n age_group_18_25 = 25\n age_group_26_35 = 35\n age_group_36_50 = 50\n age_group_51_65 = 65\n\n # Create age groups with better error handling\n try:\n df[\"age_group\"] = pd.cut(\n df[\"age\"],\n bins=[\n 0,\n age_group_18_25,\n age_group_26_35,\n age_group_36_50,\n age_group_51_65,\n 100,\n ],\n labels=[\n \"18-25\",\n \"26-35\",\n \"36-50\",\n \"51-65\",\n \"65+\",\n ],\n )\n except (ValueError, TypeError) as e:\n self.log(f\"Error creating age groups with pd.cut: {e!s}, using simple categorization\")\n df[\"age_group\"] = df[\"age\"].apply(\n lambda x: \"18-25\"\n if x <= age_group_18_25\n else \"26-35\"\n if x <= age_group_26_35\n else \"36-50\"\n if x <= age_group_36_50\n else \"51-65\"\n if x <= age_group_51_65\n else \"65+\"\n )\n\n self.log(f\"Successfully generated DataFrame with shape: {df.shape}, columns: {list(df.columns)}\")\n # CRITICAL: Use DataFrame wrapper from Langflow\n # DO NOT set self.status when returning DataFrames - it interferes with display\n return DataFrame(df)\n\n except (ValueError, TypeError) as e:\n error_msg = f\"Error generating DataFrame: {e!s}\"\n self.log(error_msg)\n # DO NOT set self.status when returning DataFrames - it interferes with display\n # Return a fallback DataFrame with error info using Langflow wrapper\n try:\n error_df = pd.DataFrame(\n {\n \"error\": [error_msg],\n \"timestamp\": [datetime.now(tz=timezone.utc).isoformat()],\n \"attempted_records\": [record_count],\n }\n )\n return DataFrame(error_df)\n except (ValueError, TypeError) as fallback_error:\n # Last resort: return simple error DataFrame\n self.log(f\"Fallback also failed: {fallback_error!s}\")\n simple_error_df = pd.DataFrame({\"error\": [error_msg]})\n return DataFrame(simple_error_df)\n"}},"tool_mode":false},"NewsSearch":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Searches Google News via RSS. Returns clean article data.","display_name":"News Search","documentation":"https://docs.langflow.org/web-search","edited":false,"field_order":["query","hl","gl","ceid","topic","location","timeout"],"frozen":false,"icon":"newspaper","legacy":true,"metadata":{"code_hash":"b8cb11f78518","dependencies":{"dependencies":[{"name":"pandas","version":"2.2.3"},{"name":"requests","version":"2.32.5"},{"name":"bs4","version":"4.12.3"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.data_source.news_search.NewsSearchComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"News Articles","group_outputs":false,"method":"search_news","name":"articles","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","ceid":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Country:Language (ceid)","dynamic":false,"info":"e.g. US:en, FR:fr. Default: US:en.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"ceid","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":"US:en"},"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":"from urllib.parse import quote_plus\n\nimport pandas as pd\nimport requests\nfrom bs4 import BeautifulSoup\n\nfrom lfx.custom import Component\nfrom lfx.io import IntInput, MessageTextInput, Output\nfrom lfx.schema import DataFrame\n\n\nclass NewsSearchComponent(Component):\n display_name = \"News Search\"\n description = \"Searches Google News via RSS. Returns clean article data.\"\n documentation: str = \"https://docs.langflow.org/web-search\"\n icon = \"newspaper\"\n name = \"NewsSearch\"\n legacy = True\n replacement = \"data.WebSearch\"\n\n inputs = [\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"Search keywords for news articles.\",\n tool_mode=True,\n required=True,\n ),\n MessageTextInput(\n name=\"hl\",\n display_name=\"Language (hl)\",\n info=\"Language code, e.g. en-US, fr, de. Default: en-US.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"gl\",\n display_name=\"Country (gl)\",\n info=\"Country code, e.g. US, FR, DE. Default: US.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"ceid\",\n display_name=\"Country:Language (ceid)\",\n info=\"e.g. US:en, FR:fr. Default: US:en.\",\n tool_mode=False,\n value=\"US:en\",\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"topic\",\n display_name=\"Topic\",\n info=\"One of: WORLD, NATION, BUSINESS, TECHNOLOGY, ENTERTAINMENT, SCIENCE, SPORTS, HEALTH.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"location\",\n display_name=\"Location (Geo)\",\n info=\"City, state, or country for location-based news. Leave blank for keyword search.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"Timeout for the request in seconds.\",\n value=5,\n required=False,\n advanced=True,\n ),\n ]\n\n outputs = [Output(name=\"articles\", display_name=\"News Articles\", method=\"search_news\")]\n\n def search_news(self) -> DataFrame:\n # Defaults\n hl = getattr(self, \"hl\", None) or \"en-US\"\n gl = getattr(self, \"gl\", None) or \"US\"\n ceid = getattr(self, \"ceid\", None) or f\"{gl}:{hl.split('-')[0]}\"\n topic = getattr(self, \"topic\", None)\n location = getattr(self, \"location\", None)\n query = getattr(self, \"query\", None)\n\n # Build base URL\n if topic:\n # Topic-based feed\n base_url = f\"https://news.google.com/rss/headlines/section/topic/{quote_plus(topic.upper())}\"\n params = f\"?hl={hl}&gl={gl}&ceid={ceid}\"\n rss_url = base_url + params\n elif location:\n # Location-based feed\n base_url = f\"https://news.google.com/rss/headlines/section/geo/{quote_plus(location)}\"\n params = f\"?hl={hl}&gl={gl}&ceid={ceid}\"\n rss_url = base_url + params\n elif query:\n # Keyword search feed\n base_url = \"https://news.google.com/rss/search?q=\"\n query_parts = [query]\n query_encoded = quote_plus(\" \".join(query_parts))\n params = f\"&hl={hl}&gl={gl}&ceid={ceid}\"\n rss_url = f\"{base_url}{query_encoded}{params}\"\n else:\n self.status = \"No search query, topic, or location provided.\"\n self.log(self.status)\n return DataFrame(\n pd.DataFrame(\n [\n {\n \"title\": \"Error\",\n \"link\": \"\",\n \"published\": \"\",\n \"summary\": \"No search query, topic, or location provided.\",\n }\n ]\n )\n )\n\n try:\n response = requests.get(rss_url, timeout=self.timeout)\n response.raise_for_status()\n soup = BeautifulSoup(response.content, \"xml\")\n items = soup.find_all(\"item\")\n except requests.RequestException as e:\n self.status = f\"Failed to fetch news: {e}\"\n self.log(self.status)\n return DataFrame(pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"published\": \"\", \"summary\": str(e)}]))\n except (AttributeError, ValueError, TypeError) as e:\n self.status = f\"Unexpected error: {e!s}\"\n self.log(self.status)\n return DataFrame(pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"published\": \"\", \"summary\": str(e)}]))\n\n if not items:\n self.status = \"No news articles found.\"\n self.log(self.status)\n return DataFrame(pd.DataFrame([{\"title\": \"No articles found\", \"link\": \"\", \"published\": \"\", \"summary\": \"\"}]))\n\n articles = []\n for item in items:\n try:\n title = self.clean_html(item.title.text if item.title else \"\")\n link = item.link.text if item.link else \"\"\n published = item.pubDate.text if item.pubDate else \"\"\n summary = self.clean_html(item.description.text if item.description else \"\")\n articles.append({\"title\": title, \"link\": link, \"published\": published, \"summary\": summary})\n except (AttributeError, ValueError, TypeError) as e:\n self.log(f\"Error parsing article: {e!s}\")\n continue\n\n df_articles = pd.DataFrame(articles)\n self.log(f\"Found {len(df_articles)} articles.\")\n return DataFrame(df_articles)\n\n def clean_html(self, html_string: str) -> str:\n return BeautifulSoup(html_string, \"html.parser\").get_text(separator=\" \", strip=True)\n"},"gl":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Country (gl)","dynamic":false,"info":"Country code, e.g. US, FR, DE. Default: US.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"gl","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":""},"hl":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Language (hl)","dynamic":false,"info":"Language code, e.g. en-US, fr, de. Default: en-US.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"hl","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":""},"location":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Location (Geo)","dynamic":false,"info":"City, state, or country for location-based news. Leave blank for keyword search.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"location","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":""},"query":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Search keywords for news articles.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"query","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":true,"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":"Timeout for the request in seconds.","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":5},"topic":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Topic","dynamic":false,"info":"One of: WORLD, NATION, BUSINESS, TECHNOLOGY, ENTERTAINMENT, SCIENCE, SPORTS, HEALTH.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"topic","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":""}},"tool_mode":false},"RSSReaderSimple":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Fetches and parses an RSS feed.","display_name":"RSS Reader","documentation":"https://docs.langflow.org/web-search","edited":false,"field_order":["rss_url","timeout"],"frozen":false,"icon":"rss","legacy":true,"metadata":{"code_hash":"6eb8fb48c9b5","dependencies":{"dependencies":[{"name":"pandas","version":"2.2.3"},{"name":"requests","version":"2.32.5"},{"name":"bs4","version":"4.12.3"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.data_source.rss.RSSReaderComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Articles","group_outputs":false,"method":"read_rss","name":"articles","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":[],"template":{"_type":"Component","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 pandas as pd\nimport requests\nfrom bs4 import BeautifulSoup\n\nfrom lfx.custom import Component\nfrom lfx.io import IntInput, MessageTextInput, Output\nfrom lfx.log.logger import logger\nfrom lfx.schema import DataFrame\n\n\nclass RSSReaderComponent(Component):\n display_name = \"RSS Reader\"\n description = \"Fetches and parses an RSS feed.\"\n documentation: str = \"https://docs.langflow.org/web-search\"\n icon = \"rss\"\n name = \"RSSReaderSimple\"\n legacy = True\n replacement = \"data.WebSearch\"\n\n inputs = [\n MessageTextInput(\n name=\"rss_url\",\n display_name=\"RSS Feed URL\",\n info=\"URL of the RSS feed to parse.\",\n tool_mode=True,\n required=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"Timeout for the RSS feed request.\",\n value=5,\n advanced=True,\n ),\n ]\n\n outputs = [Output(name=\"articles\", display_name=\"Articles\", method=\"read_rss\")]\n\n def read_rss(self) -> DataFrame:\n try:\n response = requests.get(self.rss_url, timeout=self.timeout)\n response.raise_for_status()\n if not response.content.strip():\n msg = \"Empty response received\"\n raise ValueError(msg)\n # Check if the response is valid XML\n try:\n BeautifulSoup(response.content, \"xml\")\n except Exception as e:\n msg = f\"Invalid XML response: {e}\"\n raise ValueError(msg) from e\n soup = BeautifulSoup(response.content, \"xml\")\n items = soup.find_all(\"item\")\n except (requests.RequestException, ValueError) as e:\n self.status = f\"Failed to fetch RSS: {e}\"\n return DataFrame(pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"published\": \"\", \"summary\": str(e)}]))\n\n articles = [\n {\n \"title\": item.title.text if item.title else \"\",\n \"link\": item.link.text if item.link else \"\",\n \"published\": item.pubDate.text if item.pubDate else \"\",\n \"summary\": item.description.text if item.description else \"\",\n }\n for item in items\n ]\n\n # Ensure the DataFrame has the correct columns even if empty\n df_articles = pd.DataFrame(articles, columns=[\"title\", \"link\", \"published\", \"summary\"])\n logger.info(f\"Fetched {len(df_articles)} articles.\")\n return DataFrame(df_articles)\n"},"rss_url":{"_input_type":"MessageTextInput","advanced":false,"display_name":"RSS Feed URL","dynamic":false,"info":"URL of the RSS feed to parse.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"rss_url","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":true,"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":"Timeout for the RSS feed request.","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":5}},"tool_mode":false},"SQLComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Executes SQL queries on SQLAlchemy-compatible databases.","display_name":"SQL Database","documentation":"https://docs.langflow.org/sql-database","edited":false,"field_order":["database_url","query","include_columns","add_error"],"frozen":false,"icon":"database","legacy":false,"metadata":{"code_hash":"a8dd79af50b8","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"sqlalchemy","version":"2.0.44"},{"name":"lfx","version":null}],"total_dependencies":3},"keywords":["sql","database","query","db","fetch"],"module":"lfx.components.data_source.sql_executor.SQLComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Result Table","group_outputs":false,"method":"run_sql_query","name":"run_sql_query","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","add_error":{"_input_type":"BoolInput","advanced":true,"display_name":"Add Error","dynamic":false,"info":"If True, the error will be added to the result","list":false,"list_add_label":"Add More","name":"add_error","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"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":"from typing import TYPE_CHECKING, Any\n\nfrom langchain_community.utilities import SQLDatabase\nfrom sqlalchemy.exc import SQLAlchemyError\n\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.io import BoolInput, MessageTextInput, MultilineInput, Output\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.services.cache.utils import CacheMiss\n\nif TYPE_CHECKING:\n from sqlalchemy.engine import Result\n\n\nclass SQLComponent(ComponentWithCache):\n \"\"\"A sql component.\"\"\"\n\n display_name = \"SQL Database\"\n description = \"Executes SQL queries on SQLAlchemy-compatible databases.\"\n documentation: str = \"https://docs.langflow.org/sql-database\"\n icon = \"database\"\n name = \"SQLComponent\"\n metadata = {\"keywords\": [\"sql\", \"database\", \"query\", \"db\", \"fetch\"]}\n\n def __init__(self, **kwargs) -> None:\n super().__init__(**kwargs)\n self.db: SQLDatabase = None\n\n def maybe_create_db(self):\n if self.database_url != \"\":\n if self._shared_component_cache:\n cached_db = self._shared_component_cache.get(self.database_url)\n if not isinstance(cached_db, CacheMiss):\n self.db = cached_db\n return\n self.log(\"Connecting to database\")\n try:\n self.db = SQLDatabase.from_uri(self.database_url)\n except Exception as e:\n msg = f\"An error occurred while connecting to the database: {e}\"\n raise ValueError(msg) from e\n if self._shared_component_cache:\n self._shared_component_cache.set(self.database_url, self.db)\n\n inputs = [\n MessageTextInput(name=\"database_url\", display_name=\"Database URL\", required=True),\n MultilineInput(name=\"query\", display_name=\"SQL Query\", tool_mode=True, required=True),\n BoolInput(name=\"include_columns\", display_name=\"Include Columns\", value=True, tool_mode=True, advanced=True),\n BoolInput(\n name=\"add_error\",\n display_name=\"Add Error\",\n value=False,\n tool_mode=True,\n info=\"If True, the error will be added to the result\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Result Table\", name=\"run_sql_query\", method=\"run_sql_query\"),\n ]\n\n def build_component(\n self,\n ) -> Message:\n error = None\n self.maybe_create_db()\n try:\n result = self.db.run(self.query, include_columns=self.include_columns)\n self.status = result\n except SQLAlchemyError as e:\n msg = f\"An error occurred while running the SQL Query: {e}\"\n self.log(msg)\n result = str(e)\n self.status = result\n error = repr(e)\n\n if self.add_error and error is not None:\n result = f\"{result}\\n\\nError: {error}\\n\\nQuery: {self.query}\"\n elif error is not None:\n # Then we won't add the error to the result\n result = self.query\n\n return Message(text=result)\n\n def __execute_query(self) -> list[dict[str, Any]]:\n self.maybe_create_db()\n try:\n cursor: Result[Any] = self.db.run(self.query, fetch=\"cursor\")\n return [x._asdict() for x in cursor.fetchall()]\n except SQLAlchemyError as e:\n msg = f\"An error occurred while running the SQL Query: {e}\"\n self.log(msg)\n raise ValueError(msg) from e\n\n def run_sql_query(self) -> DataFrame:\n result = self.__execute_query()\n df_result = DataFrame(result)\n self.status = df_result\n return df_result\n"},"database_url":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Database URL","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"database_url","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"include_columns":{"_input_type":"BoolInput","advanced":true,"display_name":"Include Columns","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"include_columns","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"query":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"SQL Query","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"query","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"URLComponent":{"base_classes":["DataFrame","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Fetch content from one or more web pages, following links recursively.","display_name":"URL","documentation":"https://docs.langflow.org/url","edited":false,"field_order":["urls","max_depth","prevent_outside","use_async","format","timeout","headers","filter_text_html","continue_on_failure","check_response_status","autoset_encoding"],"frozen":false,"icon":"layout-template","legacy":false,"metadata":{"code_hash":"47d3ccb92d71","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"bs4","version":"4.12.3"},{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.data_source.url.URLComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Extracted Pages","group_outputs":false,"method":"fetch_content","name":"page_results","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Raw Content","group_outputs":false,"method":"fetch_content_as_message","name":"raw_results","selected":"Message","tool_mode":false,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","autoset_encoding":{"_input_type":"BoolInput","advanced":true,"display_name":"Autoset Encoding","dynamic":false,"info":"If enabled, automatically sets the encoding of the request.","list":false,"list_add_label":"Add More","name":"autoset_encoding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"check_response_status":{"_input_type":"BoolInput","advanced":true,"display_name":"Check Response Status","dynamic":false,"info":"If enabled, checks the response status of the request.","list":false,"list_add_label":"Add More","name":"check_response_status","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"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 importlib\nimport re\n\nimport requests\nfrom bs4 import BeautifulSoup\nfrom langchain_community.document_loaders import RecursiveUrlLoader\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.helpers.data import safe_convert\nfrom lfx.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SliderInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.schema.message import Message\nfrom lfx.utils.request_utils import get_user_agent\n\n# Constants\nDEFAULT_TIMEOUT = 30\nDEFAULT_MAX_DEPTH = 1\nDEFAULT_FORMAT = \"Text\"\n\n\nURL_REGEX = re.compile(\n r\"^(https?:\\/\\/)?\" r\"(www\\.)?\" r\"([a-zA-Z0-9.-]+)\" r\"(\\.[a-zA-Z]{2,})?\" r\"(:\\d+)?\" r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n)\n\nUSER_AGENT = None\n# Check if langflow is installed using importlib.util.find_spec(name))\nif importlib.util.find_spec(\"langflow\"):\n langflow_installed = True\n USER_AGENT = get_user_agent()\nelse:\n langflow_installed = False\n USER_AGENT = \"lfx\"\n\n\nclass URLComponent(Component):\n \"\"\"A component that loads and parses content from web pages recursively.\n\n This component allows fetching content from one or more URLs, with options to:\n - Control crawl depth\n - Prevent crawling outside the root domain\n - Use async loading for better performance\n - Extract either raw HTML or clean text\n - Configure request headers and timeouts\n \"\"\"\n\n display_name = \"URL\"\n description = \"Fetch content from one or more web pages, following links recursively.\"\n documentation: str = \"https://docs.langflow.org/url\"\n icon = \"layout-template\"\n name = \"URLComponent\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n info=\"Enter one or more URLs to crawl recursively, by clicking the '+' button.\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n input_types=[],\n ),\n SliderInput(\n name=\"max_depth\",\n display_name=\"Depth\",\n info=(\n \"Controls how many 'clicks' away from the initial page the crawler will go:\\n\"\n \"- depth 1: only the initial page\\n\"\n \"- depth 2: initial page + all pages linked directly from it\\n\"\n \"- depth 3: initial page + direct links + links found on those direct link pages\\n\"\n \"Note: This is about link traversal, not URL path depth.\"\n ),\n value=DEFAULT_MAX_DEPTH,\n range_spec=RangeSpec(min=1, max=5, step=1),\n required=False,\n min_label=\" \",\n max_label=\" \",\n min_label_icon=\"None\",\n max_label_icon=\"None\",\n # slider_input=True\n ),\n BoolInput(\n name=\"prevent_outside\",\n display_name=\"Prevent Outside\",\n info=(\n \"If enabled, only crawls URLs within the same domain as the root URL. \"\n \"This helps prevent the crawler from going to external websites.\"\n ),\n value=True,\n required=False,\n advanced=True,\n ),\n BoolInput(\n name=\"use_async\",\n display_name=\"Use Async\",\n info=(\n \"If enabled, uses asynchronous loading which can be significantly faster \"\n \"but might use more system resources.\"\n ),\n value=True,\n required=False,\n advanced=True,\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=\"Output Format. Use 'Text' to extract the text from the HTML or 'HTML' for the raw HTML content.\",\n options=[\"Text\", \"HTML\"],\n value=DEFAULT_FORMAT,\n advanced=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"Timeout for the request in seconds.\",\n value=DEFAULT_TIMEOUT,\n required=False,\n advanced=True,\n ),\n TableInput(\n name=\"headers\",\n display_name=\"Headers\",\n info=\"The headers to send with the request\",\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Header\",\n \"type\": \"str\",\n \"description\": \"Header name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Header value\",\n },\n ],\n value=[{\"key\": \"User-Agent\", \"value\": USER_AGENT}],\n advanced=True,\n input_types=[\"DataFrame\"],\n ),\n BoolInput(\n name=\"filter_text_html\",\n display_name=\"Filter Text/HTML\",\n info=\"If enabled, filters out text/css content type from the results.\",\n value=True,\n required=False,\n advanced=True,\n ),\n BoolInput(\n name=\"continue_on_failure\",\n display_name=\"Continue on Failure\",\n info=\"If enabled, continues crawling even if some requests fail.\",\n value=True,\n required=False,\n advanced=True,\n ),\n BoolInput(\n name=\"check_response_status\",\n display_name=\"Check Response Status\",\n info=\"If enabled, checks the response status of the request.\",\n value=False,\n required=False,\n advanced=True,\n ),\n BoolInput(\n name=\"autoset_encoding\",\n display_name=\"Autoset Encoding\",\n info=\"If enabled, automatically sets the encoding of the request.\",\n value=True,\n required=False,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Extracted Pages\", name=\"page_results\", method=\"fetch_content\"),\n Output(display_name=\"Raw Content\", name=\"raw_results\", method=\"fetch_content_as_message\", tool_mode=False),\n ]\n\n @staticmethod\n def validate_url(url: str) -> bool:\n \"\"\"Validates if the given string matches URL pattern.\n\n Args:\n url: The URL string to validate\n\n Returns:\n bool: True if the URL is valid, False otherwise\n \"\"\"\n return bool(URL_REGEX.match(url))\n\n def ensure_url(self, url: str) -> str:\n \"\"\"Ensures the given string is a valid URL.\n\n Args:\n url: The URL string to validate and normalize\n\n Returns:\n str: The normalized URL\n\n Raises:\n ValueError: If the URL is invalid\n \"\"\"\n url = url.strip()\n if not url.startswith((\"http://\", \"https://\")):\n url = \"https://\" + url\n\n if not self.validate_url(url):\n msg = f\"Invalid URL: {url}\"\n raise ValueError(msg)\n\n return url\n\n def _create_loader(self, url: str) -> RecursiveUrlLoader:\n \"\"\"Creates a RecursiveUrlLoader instance with the configured settings.\n\n Args:\n url: The URL to load\n\n Returns:\n RecursiveUrlLoader: Configured loader instance\n \"\"\"\n headers_dict = {header[\"key\"]: header[\"value\"] for header in self.headers if header[\"value\"] is not None}\n extractor = (lambda x: x) if self.format == \"HTML\" else (lambda x: BeautifulSoup(x, \"lxml\").get_text())\n\n return RecursiveUrlLoader(\n url=url,\n max_depth=self.max_depth,\n prevent_outside=self.prevent_outside,\n use_async=self.use_async,\n extractor=extractor,\n timeout=self.timeout,\n headers=headers_dict,\n check_response_status=self.check_response_status,\n continue_on_failure=self.continue_on_failure,\n base_url=url, # Add base_url to ensure consistent domain crawling\n autoset_encoding=self.autoset_encoding, # Enable automatic encoding detection\n exclude_dirs=[], # Allow customization of excluded directories\n link_regex=None, # Allow customization of link filtering\n )\n\n def fetch_url_contents(self) -> list[dict]:\n \"\"\"Load documents from the configured URLs.\n\n Returns:\n List[Data]: List of Data objects containing the fetched content\n\n Raises:\n ValueError: If no valid URLs are provided or if there's an error loading documents\n \"\"\"\n try:\n urls = list({self.ensure_url(url) for url in self.urls if url.strip()})\n logger.debug(f\"URLs: {urls}\")\n if not urls:\n msg = \"No valid URLs provided.\"\n raise ValueError(msg)\n\n all_docs = []\n for url in urls:\n logger.debug(f\"Loading documents from {url}\")\n\n try:\n loader = self._create_loader(url)\n docs = loader.load()\n\n if not docs:\n logger.warning(f\"No documents found for {url}\")\n continue\n\n logger.debug(f\"Found {len(docs)} documents from {url}\")\n all_docs.extend(docs)\n\n except requests.exceptions.RequestException as e:\n logger.exception(f\"Error loading documents from {url}: {e}\")\n continue\n\n if not all_docs:\n msg = \"No documents were successfully loaded from any URL\"\n raise ValueError(msg)\n\n # data = [Data(text=doc.page_content, **doc.metadata) for doc in all_docs]\n data = [\n {\n \"text\": safe_convert(doc.page_content, clean_data=True),\n \"url\": doc.metadata.get(\"source\", \"\"),\n \"title\": doc.metadata.get(\"title\", \"\"),\n \"description\": doc.metadata.get(\"description\", \"\"),\n \"content_type\": doc.metadata.get(\"content_type\", \"\"),\n \"language\": doc.metadata.get(\"language\", \"\"),\n }\n for doc in all_docs\n ]\n except Exception as e:\n error_msg = e.message if hasattr(e, \"message\") else e\n msg = f\"Error loading documents: {error_msg!s}\"\n logger.exception(msg)\n raise ValueError(msg) from e\n return data\n\n def fetch_content(self) -> DataFrame:\n \"\"\"Convert the documents to a DataFrame.\"\"\"\n return DataFrame(data=self.fetch_url_contents())\n\n def fetch_content_as_message(self) -> Message:\n \"\"\"Convert the documents to a Message.\"\"\"\n url_contents = self.fetch_url_contents()\n return Message(text=\"\\n\\n\".join([x[\"text\"] for x in url_contents]), data={\"data\": url_contents})\n"},"continue_on_failure":{"_input_type":"BoolInput","advanced":true,"display_name":"Continue on Failure","dynamic":false,"info":"If enabled, continues crawling even if some requests fail.","list":false,"list_add_label":"Add More","name":"continue_on_failure","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"filter_text_html":{"_input_type":"BoolInput","advanced":true,"display_name":"Filter Text/HTML","dynamic":false,"info":"If enabled, filters out text/css content type from the results.","list":false,"list_add_label":"Add More","name":"filter_text_html","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"format":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Output Format","dynamic":false,"external_options":{},"info":"Output Format. Use 'Text' to extract the text from the HTML or 'HTML' for the raw HTML content.","name":"format","options":["Text","HTML"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Text"},"headers":{"_input_type":"TableInput","advanced":true,"display_name":"Headers","dynamic":false,"info":"The headers to send with the request","input_types":["DataFrame"],"is_list":true,"list_add_label":"Add More","name":"headers","override_skip":false,"placeholder":"","required":false,"show":true,"table_icon":"Table","table_schema":[{"description":"Header name","display_name":"Header","name":"key","type":"str"},{"description":"Header value","display_name":"Value","name":"value","type":"str"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","value":[{"key":"User-Agent","value":null}]},"max_depth":{"_input_type":"SliderInput","advanced":false,"display_name":"Depth","dynamic":false,"info":"Controls how many 'clicks' away from the initial page the crawler will go:\n- depth 1: only the initial page\n- depth 2: initial page + all pages linked directly from it\n- depth 3: initial page + direct links + links found on those direct link pages\nNote: This is about link traversal, not URL path depth.","max_label":" ","max_label_icon":"None","min_label":" ","min_label_icon":"None","name":"max_depth","override_skip":false,"placeholder":"","range_spec":{"max":5.0,"min":1.0,"step":1.0,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":1},"prevent_outside":{"_input_type":"BoolInput","advanced":true,"display_name":"Prevent Outside","dynamic":false,"info":"If enabled, only crawls URLs within the same domain as the root URL. This helps prevent the crawler from going to external websites.","list":false,"list_add_label":"Add More","name":"prevent_outside","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"timeout":{"_input_type":"IntInput","advanced":true,"display_name":"Timeout","dynamic":false,"info":"Timeout for the request in seconds.","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},"urls":{"_input_type":"MessageTextInput","advanced":false,"display_name":"URLs","dynamic":false,"info":"Enter one or more URLs to crawl recursively, by clicking the '+' button.","input_types":[],"list":true,"list_add_label":"Add URL","load_from_db":false,"name":"urls","override_skip":false,"placeholder":"Enter a URL...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"use_async":{"_input_type":"BoolInput","advanced":true,"display_name":"Use Async","dynamic":false,"info":"If enabled, uses asynchronous loading which can be significantly faster but might use more system resources.","list":false,"list_add_label":"Add More","name":"use_async","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false},"UnifiedWebSearch":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Search the web, news, or RSS feeds.","display_name":"Web Search","documentation":"https://docs.langflow.org/web-search","edited":false,"field_order":["search_mode","query","hl","gl","ceid","topic","location","timeout"],"frozen":false,"icon":"search","legacy":false,"metadata":{"code_hash":"cbeeaef8889a","dependencies":{"dependencies":[{"name":"pandas","version":"2.2.3"},{"name":"requests","version":"2.32.5"},{"name":"bs4","version":"4.12.3"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.data_source.web_search.WebSearchComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Results","group_outputs":false,"method":"perform_search","name":"results","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","ceid":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Country:Language (ceid)","dynamic":false,"info":"e.g. US:en, FR:fr. Default: US:en.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"ceid","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":"US:en"},"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":"\"\"\"Unified Web Search Component.\n\nThis component consolidates Web Search, News Search, and RSS Reader into a single\ncomponent with tabs for different search modes.\n\"\"\"\n\nimport re\nfrom typing import Any\nfrom urllib.parse import parse_qs, quote_plus, unquote, urlparse\n\nimport pandas as pd\nimport requests\nfrom bs4 import BeautifulSoup\n\nfrom lfx.custom import Component\nfrom lfx.io import IntInput, MessageTextInput, Output, TabInput\nfrom lfx.schema import DataFrame\nfrom lfx.utils.request_utils import get_user_agent\n\n\nclass WebSearchComponent(Component):\n display_name = \"Web Search\"\n description = \"Search the web, news, or RSS feeds.\"\n documentation: str = \"https://docs.langflow.org/web-search\"\n icon = \"search\"\n name = \"UnifiedWebSearch\"\n\n inputs = [\n TabInput(\n name=\"search_mode\",\n display_name=\"Search Mode\",\n options=[\"Web\", \"News\", \"RSS\"],\n info=\"Choose search mode: Web (DuckDuckGo), News (Google News), or RSS (Feed Reader)\",\n value=\"Web\",\n real_time_refresh=True,\n tool_mode=True,\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"Search keywords for news articles.\",\n tool_mode=True,\n required=True,\n ),\n MessageTextInput(\n name=\"hl\",\n display_name=\"Language (hl)\",\n info=\"Language code, e.g. en-US, fr, de. Default: en-US.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"gl\",\n display_name=\"Country (gl)\",\n info=\"Country code, e.g. US, FR, DE. Default: US.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"ceid\",\n display_name=\"Country:Language (ceid)\",\n info=\"e.g. US:en, FR:fr. Default: US:en.\",\n tool_mode=False,\n value=\"US:en\",\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"topic\",\n display_name=\"Topic\",\n info=\"One of: WORLD, NATION, BUSINESS, TECHNOLOGY, ENTERTAINMENT, SCIENCE, SPORTS, HEALTH.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n MessageTextInput(\n name=\"location\",\n display_name=\"Location (Geo)\",\n info=\"City, state, or country for location-based news. Leave blank for keyword search.\",\n tool_mode=False,\n input_types=[],\n required=False,\n advanced=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"Timeout for the request in seconds.\",\n value=5,\n required=False,\n advanced=True,\n ),\n ]\n\n outputs = [Output(name=\"results\", display_name=\"Results\", method=\"perform_search\")]\n\n def __init__(self, **kwargs):\n super().__init__(**kwargs)\n\n def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:\n \"\"\"Update input visibility based on search mode.\"\"\"\n if field_name == \"search_mode\":\n # Show/hide inputs based on search mode\n is_news = field_value == \"News\"\n is_rss = field_value == \"RSS\"\n\n # Update query field info based on mode\n if is_rss:\n build_config[\"query\"][\"info\"] = \"RSS feed URL to parse\"\n build_config[\"query\"][\"display_name\"] = \"RSS Feed URL\"\n elif is_news:\n build_config[\"query\"][\"info\"] = \"Search keywords for news articles.\"\n build_config[\"query\"][\"display_name\"] = \"Search Query\"\n else: # Web\n build_config[\"query\"][\"info\"] = \"Keywords to search for\"\n build_config[\"query\"][\"display_name\"] = \"Search Query\"\n\n # Keep news-specific fields as advanced (matching original News Search component)\n # They remain advanced=True in all modes, just like in the original component\n\n return build_config\n\n def validate_url(self, string: str) -> bool:\n \"\"\"Validate URL format.\"\"\"\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" r\"(www\\.)?\" r\"([a-zA-Z0-9.-]+)\" r\"(\\.[a-zA-Z]{2,})?\" r\"(:\\d+)?\" r\"(\\/[^\\s]*)?$\",\n re.IGNORECASE,\n )\n return bool(url_regex.match(string))\n\n def ensure_url(self, url: str) -> str:\n \"\"\"Ensure URL has proper protocol.\"\"\"\n if not url.startswith((\"http://\", \"https://\")):\n url = \"https://\" + url\n if not self.validate_url(url):\n msg = f\"Invalid URL: {url}\"\n raise ValueError(msg)\n return url\n\n def _sanitize_query(self, query: str) -> str:\n \"\"\"Sanitize search query.\"\"\"\n return re.sub(r'[<>\"\\']', \"\", query.strip())\n\n def clean_html(self, html_string: str) -> str:\n \"\"\"Remove HTML tags from text.\"\"\"\n return BeautifulSoup(html_string, \"html.parser\").get_text(separator=\" \", strip=True)\n\n def perform_web_search(self) -> DataFrame:\n \"\"\"Perform DuckDuckGo web search.\"\"\"\n query = self._sanitize_query(self.query)\n if not query:\n msg = \"Empty search query\"\n raise ValueError(msg)\n\n headers = {\"User-Agent\": get_user_agent()}\n params = {\"q\": query, \"kl\": \"us-en\"}\n url = \"https://html.duckduckgo.com/html/\"\n\n try:\n response = requests.get(url, params=params, headers=headers, timeout=self.timeout)\n response.raise_for_status()\n except requests.RequestException as e:\n self.status = f\"Failed request: {e!s}\"\n return DataFrame(pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"snippet\": str(e), \"content\": \"\"}]))\n\n if not response.text or \"text/html\" not in response.headers.get(\"content-type\", \"\").lower():\n self.status = \"No results found\"\n return DataFrame(\n pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"snippet\": \"No results found\", \"content\": \"\"}])\n )\n\n soup = BeautifulSoup(response.text, \"html.parser\")\n results = []\n\n for result in soup.select(\"div.result\"):\n title_tag = result.select_one(\"a.result__a\")\n snippet_tag = result.select_one(\"a.result__snippet\")\n if title_tag:\n raw_link = title_tag.get(\"href\", \"\")\n parsed = urlparse(raw_link)\n uddg = parse_qs(parsed.query).get(\"uddg\", [\"\"])[0]\n decoded_link = unquote(uddg) if uddg else raw_link\n\n try:\n final_url = self.ensure_url(decoded_link)\n page = requests.get(final_url, headers=headers, timeout=self.timeout)\n page.raise_for_status()\n content = BeautifulSoup(page.text, \"lxml\").get_text(separator=\" \", strip=True)\n except requests.RequestException as e:\n final_url = decoded_link\n content = f\"(Failed to fetch: {e!s}\"\n\n results.append(\n {\n \"title\": title_tag.get_text(strip=True),\n \"link\": final_url,\n \"snippet\": snippet_tag.get_text(strip=True) if snippet_tag else \"\",\n \"content\": content,\n }\n )\n\n return DataFrame(pd.DataFrame(results))\n\n def perform_news_search(self) -> DataFrame:\n \"\"\"Perform Google News search.\"\"\"\n query = getattr(self, \"query\", \"\")\n hl = getattr(self, \"hl\", \"en-US\") or \"en-US\"\n gl = getattr(self, \"gl\", \"US\") or \"US\"\n topic = getattr(self, \"topic\", None)\n location = getattr(self, \"location\", None)\n\n ceid = f\"{gl}:{hl.split('-')[0]}\"\n\n # Build RSS URL based on parameters\n if topic:\n # Topic-based feed\n base_url = f\"https://news.google.com/rss/headlines/section/topic/{quote_plus(topic.upper())}\"\n params = f\"?hl={hl}&gl={gl}&ceid={ceid}\"\n rss_url = base_url + params\n elif location:\n # Location-based feed\n base_url = f\"https://news.google.com/rss/headlines/section/geo/{quote_plus(location)}\"\n params = f\"?hl={hl}&gl={gl}&ceid={ceid}\"\n rss_url = base_url + params\n elif query:\n # Keyword search feed\n base_url = \"https://news.google.com/rss/search?q=\"\n query_encoded = quote_plus(query)\n params = f\"&hl={hl}&gl={gl}&ceid={ceid}\"\n rss_url = f\"{base_url}{query_encoded}{params}\"\n else:\n self.status = \"No search query, topic, or location provided.\"\n return DataFrame(\n pd.DataFrame(\n [{\"title\": \"Error\", \"link\": \"\", \"published\": \"\", \"summary\": \"No search parameters provided\"}]\n )\n )\n\n try:\n response = requests.get(rss_url, timeout=self.timeout)\n response.raise_for_status()\n soup = BeautifulSoup(response.content, \"xml\")\n items = soup.find_all(\"item\")\n except requests.RequestException as e:\n self.status = f\"Failed to fetch news: {e}\"\n return DataFrame(pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"published\": \"\", \"summary\": str(e)}]))\n\n if not items:\n self.status = \"No news articles found.\"\n return DataFrame(pd.DataFrame([{\"title\": \"No articles found\", \"link\": \"\", \"published\": \"\", \"summary\": \"\"}]))\n\n articles = []\n for item in items:\n try:\n title = self.clean_html(item.title.text if item.title else \"\")\n link = item.link.text if item.link else \"\"\n published = item.pubDate.text if item.pubDate else \"\"\n summary = self.clean_html(item.description.text if item.description else \"\")\n articles.append({\"title\": title, \"link\": link, \"published\": published, \"summary\": summary})\n except (AttributeError, ValueError, TypeError) as e:\n self.log(f\"Error parsing article: {e!s}\")\n continue\n\n return DataFrame(pd.DataFrame(articles))\n\n def perform_rss_read(self) -> DataFrame:\n \"\"\"Read RSS feed.\"\"\"\n rss_url = getattr(self, \"query\", \"\")\n if not rss_url:\n return DataFrame(\n pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"published\": \"\", \"summary\": \"No RSS URL provided\"}])\n )\n\n try:\n response = requests.get(rss_url, timeout=self.timeout)\n response.raise_for_status()\n if not response.content.strip():\n msg = \"Empty response received\"\n raise ValueError(msg)\n\n # Validate XML\n try:\n BeautifulSoup(response.content, \"xml\")\n except Exception as e:\n msg = f\"Invalid XML response: {e}\"\n raise ValueError(msg) from e\n\n soup = BeautifulSoup(response.content, \"xml\")\n items = soup.find_all(\"item\")\n except (requests.RequestException, ValueError) as e:\n self.status = f\"Failed to fetch RSS: {e}\"\n return DataFrame(pd.DataFrame([{\"title\": \"Error\", \"link\": \"\", \"published\": \"\", \"summary\": str(e)}]))\n\n articles = [\n {\n \"title\": item.title.text if item.title else \"\",\n \"link\": item.link.text if item.link else \"\",\n \"published\": item.pubDate.text if item.pubDate else \"\",\n \"summary\": item.description.text if item.description else \"\",\n }\n for item in items\n ]\n\n # Ensure DataFrame has correct columns even if empty\n df_articles = pd.DataFrame(articles, columns=[\"title\", \"link\", \"published\", \"summary\"])\n self.log(f\"Fetched {len(df_articles)} articles.\")\n return DataFrame(df_articles)\n\n def perform_search(self) -> DataFrame:\n \"\"\"Main search method that routes to appropriate search function based on mode.\"\"\"\n search_mode = getattr(self, \"search_mode\", \"Web\")\n\n if search_mode == \"Web\":\n return self.perform_web_search()\n if search_mode == \"News\":\n return self.perform_news_search()\n if search_mode == \"RSS\":\n return self.perform_rss_read()\n # Fallback to web search\n return self.perform_web_search()\n"},"gl":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Country (gl)","dynamic":false,"info":"Country code, e.g. US, FR, DE. Default: US.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"gl","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":""},"hl":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Language (hl)","dynamic":false,"info":"Language code, e.g. en-US, fr, de. Default: en-US.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"hl","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":""},"location":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Location (Geo)","dynamic":false,"info":"City, state, or country for location-based news. Leave blank for keyword search.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"location","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":""},"query":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Search keywords for news articles.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"query","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"search_mode":{"_input_type":"TabInput","advanced":false,"display_name":"Search Mode","dynamic":false,"info":"Choose search mode: Web (DuckDuckGo), News (Google News), or RSS (Feed Reader)","name":"search_mode","options":["Web","News","RSS"],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_metadata":true,"track_in_telemetry":true,"type":"tab","value":"Web"},"timeout":{"_input_type":"IntInput","advanced":true,"display_name":"Timeout","dynamic":false,"info":"Timeout for the request in seconds.","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":5},"topic":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Topic","dynamic":false,"info":"One of: WORLD, NATION, BUSINESS, TECHNOLOGY, ENTERTAINMENT, SCIENCE, SPORTS, HEALTH.","input_types":[],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"topic","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":""}},"tool_mode":false}}],["datastax",{"AssistantsCreateAssistant":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Creates an Assistant and returns it's id","display_name":"Create Assistant","documentation":"","edited":false,"field_order":["assistant_name","instructions","model","env_set"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"8d9869d9a89d","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.datastax.create_assistant.AssistantsCreateAssistant"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Assistant ID","group_outputs":false,"method":"process_inputs","name":"assistant_id","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","assistant_name":{"_input_type":"StrInput","advanced":false,"display_name":"Assistant Name","dynamic":false,"info":"Name for the assistant being created","list":false,"list_add_label":"Add More","load_from_db":false,"name":"assistant_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"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":"from lfx.base.astra_assistants.util import get_patched_openai_client\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.inputs.inputs import MultilineInput, StrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\n\n\nclass AssistantsCreateAssistant(ComponentWithCache):\n icon = \"AstraDB\"\n display_name = \"Create Assistant\"\n description = \"Creates an Assistant and returns it's id\"\n legacy = True\n\n inputs = [\n StrInput(\n name=\"assistant_name\",\n display_name=\"Assistant Name\",\n info=\"Name for the assistant being created\",\n ),\n StrInput(\n name=\"instructions\",\n display_name=\"Instructions\",\n info=\"Instructions for the assistant, think of these as the system prompt.\",\n ),\n StrInput(\n name=\"model\",\n display_name=\"Model name\",\n info=(\n \"Model for the assistant.\\n\\n\"\n \"Environment variables for provider credentials can be set with the Dotenv Component.\\n\\n\"\n \"Models are supported via LiteLLM, \"\n \"see (https://docs.litellm.ai/docs/providers) for supported model names and env vars.\"\n ),\n # refresh_model=True\n ),\n MultilineInput(\n name=\"env_set\",\n display_name=\"Environment Set\",\n info=\"Dummy input to allow chaining with Dotenv Component.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Assistant ID\", name=\"assistant_id\", method=\"process_inputs\"),\n ]\n\n def __init__(self, **kwargs) -> None:\n super().__init__(**kwargs)\n self.client = get_patched_openai_client(self._shared_component_cache)\n\n def process_inputs(self) -> Message:\n logger.info(f\"env_set is {self.env_set}\")\n assistant = self.client.beta.assistants.create(\n name=self.assistant_name,\n instructions=self.instructions,\n model=self.model,\n )\n return Message(text=assistant.id)\n"},"env_set":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Environment Set","dynamic":false,"info":"Dummy input to allow chaining with Dotenv Component.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"env_set","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":""},"instructions":{"_input_type":"StrInput","advanced":false,"display_name":"Instructions","dynamic":false,"info":"Instructions for the assistant, think of these as the system prompt.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"instructions","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"model":{"_input_type":"StrInput","advanced":false,"display_name":"Model name","dynamic":false,"info":"Model for the assistant.\n\nEnvironment variables for provider credentials can be set with the Dotenv Component.\n\nModels are supported via LiteLLM, see (https://docs.litellm.ai/docs/providers) for supported model names and env vars.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"model","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"AssistantsCreateThread":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Creates a thread and returns the thread id","display_name":"Create Assistant Thread","documentation":"","edited":false,"field_order":["env_set"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"5d40a73accfd","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.datastax.create_thread.AssistantsCreateThread"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Thread ID","group_outputs":false,"method":"process_inputs","name":"thread_id","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from lfx.base.astra_assistants.util import get_patched_openai_client\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.inputs.inputs import MultilineInput\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\n\n\nclass AssistantsCreateThread(ComponentWithCache):\n display_name = \"Create Assistant Thread\"\n description = \"Creates a thread and returns the thread id\"\n icon = \"AstraDB\"\n legacy = True\n inputs = [\n MultilineInput(\n name=\"env_set\",\n display_name=\"Environment Set\",\n info=\"Dummy input to allow chaining with Dotenv Component.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Thread ID\", name=\"thread_id\", method=\"process_inputs\"),\n ]\n\n def __init__(self, **kwargs) -> None:\n super().__init__(**kwargs)\n self.client = get_patched_openai_client(self._shared_component_cache)\n\n def process_inputs(self) -> Message:\n thread = self.client.beta.threads.create()\n thread_id = thread.id\n\n return Message(text=thread_id)\n"},"env_set":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Environment Set","dynamic":false,"info":"Dummy input to allow chaining with Dotenv Component.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"env_set","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":""}},"tool_mode":false},"AssistantsGetAssistantName":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Assistant by id","display_name":"Get Assistant name","documentation":"","edited":false,"field_order":["assistant_id","env_set"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"1f60da161fd3","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.datastax.get_assistant.AssistantsGetAssistantName"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Assistant Name","group_outputs":false,"method":"process_inputs","name":"assistant_name","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","assistant_id":{"_input_type":"StrInput","advanced":false,"display_name":"Assistant ID","dynamic":false,"info":"ID of the assistant","list":false,"list_add_label":"Add More","load_from_db":false,"name":"assistant_id","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"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":"from lfx.base.astra_assistants.util import get_patched_openai_client\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.inputs.inputs import MultilineInput, StrInput\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\n\n\nclass AssistantsGetAssistantName(ComponentWithCache):\n display_name = \"Get Assistant name\"\n description = \"Assistant by id\"\n icon = \"AstraDB\"\n legacy = True\n inputs = [\n StrInput(\n name=\"assistant_id\",\n display_name=\"Assistant ID\",\n info=\"ID of the assistant\",\n ),\n MultilineInput(\n name=\"env_set\",\n display_name=\"Environment Set\",\n info=\"Dummy input to allow chaining with Dotenv Component.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Assistant Name\", name=\"assistant_name\", method=\"process_inputs\"),\n ]\n\n def __init__(self, **kwargs) -> None:\n super().__init__(**kwargs)\n self.client = get_patched_openai_client(self._shared_component_cache)\n\n def process_inputs(self) -> Message:\n assistant = self.client.beta.assistants.retrieve(\n assistant_id=self.assistant_id,\n )\n return Message(text=assistant.name)\n"},"env_set":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Environment Set","dynamic":false,"info":"Dummy input to allow chaining with Dotenv Component.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"env_set","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":""}},"tool_mode":false},"AssistantsListAssistants":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Returns a list of assistant id's","display_name":"List Assistants","documentation":"","edited":false,"field_order":[],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"17e9c5c78a6e","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.datastax.list_assistants.AssistantsListAssistants"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Assistants","group_outputs":false,"method":"process_inputs","name":"assistants","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from lfx.base.astra_assistants.util import get_patched_openai_client\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\n\n\nclass AssistantsListAssistants(ComponentWithCache):\n display_name = \"List Assistants\"\n description = \"Returns a list of assistant id's\"\n icon = \"AstraDB\"\n legacy = True\n outputs = [\n Output(display_name=\"Assistants\", name=\"assistants\", method=\"process_inputs\"),\n ]\n\n def __init__(self, **kwargs) -> None:\n super().__init__(**kwargs)\n self.client = get_patched_openai_client(self._shared_component_cache)\n\n def process_inputs(self) -> Message:\n assistants = self.client.beta.assistants.list().data\n id_list = [assistant.id for assistant in assistants]\n return Message(\n # get text from list\n text=\"\\n\".join(id_list)\n )\n"}},"tool_mode":false},"AssistantsRun":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Executes an Assistant Run against a thread","display_name":"Run Assistant","documentation":"","edited":false,"field_order":["assistant_id","user_message","thread_id","env_set"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"5e219cd290d3","dependencies":{"dependencies":[{"name":"openai","version":"1.82.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.datastax.run.AssistantsRun"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Assistant Response","group_outputs":false,"method":"process_inputs","name":"assistant_response","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","assistant_id":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Assistant ID","dynamic":false,"info":"The ID of the assistant to run. \n\nCan be retrieved using the List Assistants component or created with the Create Assistant component.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"assistant_id","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":""},"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":"from typing import Any\n\nfrom openai.lib.streaming import AssistantEventHandler\n\nfrom lfx.base.astra_assistants.util import get_patched_openai_client\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.inputs.inputs import MultilineInput\nfrom lfx.schema.dotdict import dotdict\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\n\n\nclass AssistantsRun(ComponentWithCache):\n display_name = \"Run Assistant\"\n description = \"Executes an Assistant Run against a thread\"\n icon = \"AstraDB\"\n legacy = True\n\n def __init__(self, **kwargs) -> None:\n super().__init__(**kwargs)\n self.client = get_patched_openai_client(self._shared_component_cache)\n self.thread_id = None\n\n def update_build_config(\n self,\n build_config: dotdict,\n field_value: Any,\n field_name: str | None = None,\n ) -> None:\n if field_name == \"thread_id\":\n if field_value is None:\n thread = self.client.beta.threads.create()\n self.thread_id = thread.id\n build_config[\"thread_id\"] = field_value\n\n inputs = [\n MultilineInput(\n name=\"assistant_id\",\n display_name=\"Assistant ID\",\n info=(\n \"The ID of the assistant to run. \\n\\n\"\n \"Can be retrieved using the List Assistants component or created with the Create Assistant component.\"\n ),\n ),\n MultilineInput(\n name=\"user_message\",\n display_name=\"User Message\",\n info=\"User message to pass to the run.\",\n ),\n MultilineInput(\n name=\"thread_id\",\n display_name=\"Thread ID\",\n required=False,\n info=\"Thread ID to use with the run. If not provided, a new thread will be created.\",\n ),\n MultilineInput(\n name=\"env_set\",\n display_name=\"Environment Set\",\n info=\"Dummy input to allow chaining with Dotenv Component.\",\n ),\n ]\n\n outputs = [Output(display_name=\"Assistant Response\", name=\"assistant_response\", method=\"process_inputs\")]\n\n def process_inputs(self) -> Message:\n text = \"\"\n\n if self.thread_id is None:\n thread = self.client.beta.threads.create()\n self.thread_id = thread.id\n\n # add the user message\n self.client.beta.threads.messages.create(thread_id=self.thread_id, role=\"user\", content=self.user_message)\n\n class EventHandler(AssistantEventHandler):\n def __init__(self) -> None:\n super().__init__()\n\n def on_exception(self, exception: Exception) -> None:\n raise exception\n\n event_handler = EventHandler()\n with self.client.beta.threads.runs.create_and_stream(\n thread_id=self.thread_id,\n assistant_id=self.assistant_id,\n event_handler=event_handler,\n ) as stream:\n for part in stream.text_deltas:\n text += part\n return Message(text=text)\n"},"env_set":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Environment Set","dynamic":false,"info":"Dummy input to allow chaining with Dotenv Component.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"env_set","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":""},"thread_id":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Thread ID","dynamic":false,"info":"Thread ID to use with the run. If not provided, a new thread will be created.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"thread_id","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":""},"user_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"User Message","dynamic":false,"info":"User message to pass to the run.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"user_message","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":""}},"tool_mode":false},"Astra Assistant Agent":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Manages Assistant Interactions","display_name":"Astra Assistant Agent","documentation":"","edited":false,"field_order":["model_name","instructions","input_tools","user_message","file","input_thread_id","input_assistant_id","env_set"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"8d4ac1cee37a","dependencies":{"dependencies":[{"name":"astra_assistants","version":"2.2.13"},{"name":"langchain_core","version":"0.3.80"},{"name":"lfx","version":null}],"total_dependencies":3},"module":"lfx.components.datastax.astradb_assistant_manager.AstraAssistantManager"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Assistant Response","group_outputs":false,"method":"get_assistant_response","name":"assistant_response","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool output","group_outputs":false,"hidden":true,"method":"get_tool_output","name":"tool_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Thread Id","group_outputs":false,"hidden":true,"method":"get_thread_id","name":"output_thread_id","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Assistant Id","group_outputs":false,"hidden":true,"method":"get_assistant_id","name":"output_assistant_id","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Vector Store Id","group_outputs":false,"hidden":true,"method":"get_vs_id","name":"output_vs_id","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 asyncio\nfrom asyncio import to_thread\nfrom typing import TYPE_CHECKING, Any, cast\n\nfrom astra_assistants.astra_assistants_manager import AssistantManager\nfrom langchain_core.agents import AgentFinish\n\nfrom lfx.base.agents.events import ExceptionWithMessageError, process_agent_events\nfrom lfx.base.astra_assistants.util import (\n get_patched_openai_client,\n litellm_model_names,\n sync_upload,\n wrap_base_tool_as_tool_interface,\n)\nfrom lfx.custom.custom_component.component_with_cache import ComponentWithCache\nfrom lfx.inputs.inputs import DropdownInput, FileInput, HandleInput, MultilineInput\nfrom lfx.log.logger import logger\nfrom lfx.memory import delete_message\nfrom lfx.schema.content_block import ContentBlock\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\nfrom lfx.utils.constants import MESSAGE_SENDER_AI\n\nif TYPE_CHECKING:\n from lfx.schema.log import SendMessageFunctionType\n\n\nclass AstraAssistantManager(ComponentWithCache):\n display_name = \"Astra Assistant Agent\"\n name = \"Astra Assistant Agent\"\n description = \"Manages Assistant Interactions\"\n icon = \"AstraDB\"\n legacy = True\n\n inputs = [\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model\",\n advanced=False,\n options=litellm_model_names,\n value=\"gpt-4o-mini\",\n ),\n MultilineInput(\n name=\"instructions\",\n display_name=\"Agent Instructions\",\n info=\"Instructions for the assistant, think of these as the system prompt.\",\n ),\n HandleInput(\n name=\"input_tools\",\n display_name=\"Tools\",\n input_types=[\"Tool\"],\n is_list=True,\n required=False,\n info=\"These are the tools that the agent can use to help with tasks.\",\n ),\n # DropdownInput(\n # display_name=\"Tools\",\n # name=\"tool\",\n # options=tool_names,\n # ),\n MultilineInput(\n name=\"user_message\", display_name=\"User Message\", info=\"User message to pass to the run.\", tool_mode=True\n ),\n FileInput(\n name=\"file\",\n display_name=\"File(s) for retrieval\",\n list=True,\n info=\"Files to be sent with the message.\",\n required=False,\n show=True,\n file_types=[\n \"txt\",\n \"md\",\n \"mdx\",\n \"csv\",\n \"json\",\n \"yaml\",\n \"yml\",\n \"xml\",\n \"html\",\n \"htm\",\n \"pdf\",\n \"docx\",\n \"py\",\n \"sh\",\n \"sql\",\n \"js\",\n \"ts\",\n \"tsx\",\n \"jpg\",\n \"jpeg\",\n \"png\",\n \"bmp\",\n \"image\",\n \"zip\",\n \"tar\",\n \"tgz\",\n \"bz2\",\n \"gz\",\n \"c\",\n \"cpp\",\n \"cs\",\n \"css\",\n \"go\",\n \"java\",\n \"php\",\n \"rb\",\n \"tex\",\n \"doc\",\n \"docx\",\n \"ppt\",\n \"pptx\",\n \"xls\",\n \"xlsx\",\n \"jsonl\",\n ],\n ),\n MultilineInput(\n name=\"input_thread_id\",\n display_name=\"Thread ID (optional)\",\n info=\"ID of the thread\",\n advanced=True,\n ),\n MultilineInput(\n name=\"input_assistant_id\",\n display_name=\"Assistant ID (optional)\",\n info=\"ID of the assistant\",\n advanced=True,\n ),\n MultilineInput(\n name=\"env_set\",\n display_name=\"Environment Set\",\n info=\"Dummy input to allow chaining with Dotenv Component.\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Assistant Response\", name=\"assistant_response\", method=\"get_assistant_response\"),\n Output(display_name=\"Tool output\", name=\"tool_output\", method=\"get_tool_output\", hidden=True),\n Output(display_name=\"Thread Id\", name=\"output_thread_id\", method=\"get_thread_id\", hidden=True),\n Output(display_name=\"Assistant Id\", name=\"output_assistant_id\", method=\"get_assistant_id\", hidden=True),\n Output(display_name=\"Vector Store Id\", name=\"output_vs_id\", method=\"get_vs_id\", hidden=True),\n ]\n\n def __init__(self, **kwargs) -> None:\n super().__init__(**kwargs)\n self.lock = asyncio.Lock()\n self.initialized: bool = False\n self._assistant_response: Message = None # type: ignore[assignment]\n self._tool_output: Message = None # type: ignore[assignment]\n self._thread_id: Message = None # type: ignore[assignment]\n self._assistant_id: Message = None # type: ignore[assignment]\n self._vs_id: Message = None # type: ignore[assignment]\n self.client = get_patched_openai_client(self._shared_component_cache)\n self.input_tools: list[Any]\n\n async def get_assistant_response(self) -> Message:\n await self.initialize()\n self.status = self._assistant_response\n return self._assistant_response\n\n async def get_vs_id(self) -> Message:\n await self.initialize()\n self.status = self._vs_id\n return self._vs_id\n\n async def get_tool_output(self) -> Message:\n await self.initialize()\n self.status = self._tool_output\n return self._tool_output\n\n async def get_thread_id(self) -> Message:\n await self.initialize()\n self.status = self._thread_id\n return self._thread_id\n\n async def get_assistant_id(self) -> Message:\n await self.initialize()\n self.status = self._assistant_id\n return self._assistant_id\n\n async def initialize(self) -> None:\n async with self.lock:\n if not self.initialized:\n await self.process_inputs()\n self.initialized = True\n\n async def process_inputs(self) -> None:\n await logger.ainfo(f\"env_set is {self.env_set}\")\n await logger.ainfo(self.input_tools)\n tools = []\n tool_obj = None\n if self.input_tools is None:\n self.input_tools = []\n for tool in self.input_tools:\n tool_obj = wrap_base_tool_as_tool_interface(tool)\n tools.append(tool_obj)\n\n assistant_id = None\n thread_id = None\n if self.input_assistant_id:\n assistant_id = self.input_assistant_id\n if self.input_thread_id:\n thread_id = self.input_thread_id\n\n if hasattr(self, \"graph\"):\n session_id = self.graph.session_id\n elif hasattr(self, \"_session_id\"):\n session_id = self._session_id\n else:\n session_id = None\n\n agent_message = Message(\n sender=MESSAGE_SENDER_AI,\n sender_name=self.display_name or \"Astra Assistant\",\n properties={\"icon\": \"Bot\", \"state\": \"partial\"},\n content_blocks=[ContentBlock(title=\"Assistant Steps\", contents=[])],\n session_id=session_id,\n )\n\n assistant_manager = AssistantManager(\n instructions=self.instructions,\n model=self.model_name,\n name=\"managed_assistant\",\n tools=tools,\n client=self.client,\n thread_id=thread_id,\n assistant_id=assistant_id,\n )\n\n if self.file:\n file = await to_thread(sync_upload, self.file, assistant_manager.client)\n vector_store = assistant_manager.client.beta.vector_stores.create(name=\"my_vs\", file_ids=[file.id])\n assistant_tools = assistant_manager.assistant.tools\n assistant_tools += [{\"type\": \"file_search\"}]\n assistant = assistant_manager.client.beta.assistants.update(\n assistant_manager.assistant.id,\n tools=assistant_tools,\n tool_resources={\"file_search\": {\"vector_store_ids\": [vector_store.id]}},\n )\n assistant_manager.assistant = assistant\n\n async def step_iterator():\n # Initial event\n yield {\"event\": \"on_chain_start\", \"name\": \"AstraAssistant\", \"data\": {\"input\": {\"text\": self.user_message}}}\n\n content = self.user_message\n result = await assistant_manager.run_thread(content=content, tool=tool_obj)\n\n # Tool usage if present\n if \"output\" in result and \"arguments\" in result:\n yield {\"event\": \"on_tool_start\", \"name\": \"tool\", \"data\": {\"input\": {\"text\": str(result[\"arguments\"])}}}\n yield {\"event\": \"on_tool_end\", \"name\": \"tool\", \"data\": {\"output\": result[\"output\"]}}\n\n if \"file_search\" in result and result[\"file_search\"] is not None:\n yield {\"event\": \"on_tool_start\", \"name\": \"tool\", \"data\": {\"input\": {\"text\": self.user_message}}}\n file_search_str = \"\"\n for chunk in result[\"file_search\"].to_dict().get(\"chunks\", []):\n file_search_str += f\"## Chunk ID: `{chunk['chunk_id']}`\\n\"\n file_search_str += f\"**Content:**\\n\\n```\\n{chunk['content']}\\n```\\n\\n\"\n if \"score\" in chunk:\n file_search_str += f\"**Score:** {chunk['score']}\\n\\n\"\n if \"file_id\" in chunk:\n file_search_str += f\"**File ID:** `{chunk['file_id']}`\\n\\n\"\n if \"file_name\" in chunk:\n file_search_str += f\"**File Name:** `{chunk['file_name']}`\\n\\n\"\n if \"bytes\" in chunk:\n file_search_str += f\"**Bytes:** {chunk['bytes']}\\n\\n\"\n if \"search_string\" in chunk:\n file_search_str += f\"**Search String:** {chunk['search_string']}\\n\\n\"\n yield {\"event\": \"on_tool_end\", \"name\": \"tool\", \"data\": {\"output\": file_search_str}}\n\n if \"text\" not in result:\n msg = f\"No text in result, {result}\"\n raise ValueError(msg)\n\n self._assistant_response = Message(text=result[\"text\"])\n if \"decision\" in result:\n self._tool_output = Message(text=str(result[\"decision\"].is_complete))\n else:\n self._tool_output = Message(text=result[\"text\"])\n self._thread_id = Message(text=assistant_manager.thread.id)\n self._assistant_id = Message(text=assistant_manager.assistant.id)\n\n # Final event - format it like AgentFinish to match the expected format\n yield {\n \"event\": \"on_chain_end\",\n \"name\": \"AstraAssistant\",\n \"data\": {\"output\": AgentFinish(return_values={\"output\": result[\"text\"]}, log=\"\")},\n }\n\n try:\n if hasattr(self, \"send_message\"):\n processed_result = await process_agent_events(\n step_iterator(),\n agent_message,\n cast(\"SendMessageFunctionType\", self.send_message),\n )\n self.status = processed_result\n except ExceptionWithMessageError as e:\n msg_id = e.agent_message.id\n await delete_message(id_=msg_id)\n await self._send_message_event(e.agent_message, category=\"remove_message\")\n raise\n except Exception:\n raise\n"},"env_set":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Environment Set","dynamic":false,"info":"Dummy input to allow chaining with Dotenv Component.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"env_set","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":""},"file":{"_input_type":"FileInput","advanced":false,"display_name":"File(s) for retrieval","dynamic":false,"fileTypes":["txt","md","mdx","csv","json","yaml","yml","xml","html","htm","pdf","docx","py","sh","sql","js","ts","tsx","jpg","jpeg","png","bmp","image","zip","tar","tgz","bz2","gz","c","cpp","cs","css","go","java","php","rb","tex","doc","docx","ppt","pptx","xls","xlsx","jsonl"],"file_path":"","info":"Files to be sent with the message.","list":true,"list_add_label":"Add More","name":"file","override_skip":false,"placeholder":"","required":false,"show":true,"temp_file":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"file","value":""},"input_assistant_id":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Assistant ID (optional)","dynamic":false,"info":"ID of the assistant","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"input_assistant_id","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_thread_id":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"Thread ID (optional)","dynamic":false,"info":"ID of the thread","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"input_thread_id","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_tools":{"_input_type":"HandleInput","advanced":false,"display_name":"Tools","dynamic":false,"info":"These are the tools that the agent can use to help with tasks.","input_types":["Tool"],"list":true,"list_add_label":"Add More","name":"input_tools","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"instructions":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Agent Instructions","dynamic":false,"info":"Instructions for the assistant, think of these as the system prompt.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"instructions","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":""},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model","dynamic":false,"external_options":{},"info":"","name":"model_name","options":["1024-x-1024/50-steps/bedrock/amazon.nova-canvas-v1:0","1024-x-1024/50-steps/stability.stable-diffusion-xl-v1","1024-x-1024/dall-e-2","1024-x-1024/max-steps/stability.stable-diffusion-xl-v1","256-x-256/dall-e-2","512-x-512/50-steps/stability.stable-diffusion-xl-v0","512-x-512/dall-e-2","512-x-512/max-steps/stability.stable-diffusion-xl-v0","ai21.j2-mid-v1","ai21.j2-ultra-v1","ai21.jamba-1-5-large-v1:0","ai21.jamba-1-5-mini-v1:0","ai21.jamba-instruct-v1:0","aiml/dall-e-2","aiml/dall-e-3","aiml/flux-pro","aiml/flux-pro/v1.1","aiml/flux-pro/v1.1-ultra","aiml/flux-realism","aiml/flux/dev","aiml/flux/kontext-max/text-to-image","aiml/flux/kontext-pro/text-to-image","aiml/flux/schnell","amazon.nova-canvas-v1:0","us.writer.palmyra-x4-v1:0","us.writer.palmyra-x5-v1:0","writer.palmyra-x4-v1:0","writer.palmyra-x5-v1:0","amazon.nova-lite-v1:0","amazon.nova-2-lite-v1:0","apac.amazon.nova-2-lite-v1:0","eu.amazon.nova-2-lite-v1:0","us.amazon.nova-2-lite-v1:0","amazon.nova-micro-v1:0","amazon.nova-pro-v1:0","amazon.rerank-v1:0","amazon.titan-embed-image-v1","amazon.titan-embed-text-v1","amazon.titan-embed-text-v2:0","amazon.titan-image-generator-v1","amazon.titan-image-generator-v2","amazon.titan-image-generator-v2:0","twelvelabs.marengo-embed-2-7-v1:0","us.twelvelabs.marengo-embed-2-7-v1:0","eu.twelvelabs.marengo-embed-2-7-v1:0","twelvelabs.pegasus-1-2-v1:0","us.twelvelabs.pegasus-1-2-v1:0","eu.twelvelabs.pegasus-1-2-v1:0","amazon.titan-text-express-v1","amazon.titan-text-lite-v1","amazon.titan-text-premier-v1:0","anthropic.claude-3-5-haiku-20241022-v1:0","anthropic.claude-haiku-4-5-20251001-v1:0","anthropic.claude-haiku-4-5@20251001","anthropic.claude-3-5-sonnet-20240620-v1:0","anthropic.claude-3-5-sonnet-20241022-v2:0","anthropic.claude-3-7-sonnet-20240620-v1:0","anthropic.claude-3-7-sonnet-20250219-v1:0","anthropic.claude-3-haiku-20240307-v1:0","anthropic.claude-3-opus-20240229-v1:0","anthropic.claude-3-sonnet-20240229-v1:0","anthropic.claude-instant-v1","anthropic.claude-opus-4-1-20250805-v1:0","anthropic.claude-opus-4-20250514-v1:0","anthropic.claude-opus-4-5-20251101-v1:0","anthropic.claude-sonnet-4-20250514-v1:0","anthropic.claude-sonnet-4-5-20250929-v1:0","anthropic.claude-v1","anthropic.claude-v2:1","anyscale/HuggingFaceH4/zephyr-7b-beta","anyscale/codellama/CodeLlama-34b-Instruct-hf","anyscale/codellama/CodeLlama-70b-Instruct-hf","anyscale/google/gemma-7b-it","anyscale/meta-llama/Llama-2-13b-chat-hf","anyscale/meta-llama/Llama-2-70b-chat-hf","anyscale/meta-llama/Llama-2-7b-chat-hf","anyscale/meta-llama/Meta-Llama-3-70B-Instruct","anyscale/meta-llama/Meta-Llama-3-8B-Instruct","anyscale/mistralai/Mistral-7B-Instruct-v0.1","anyscale/mistralai/Mixtral-8x22B-Instruct-v0.1","anyscale/mistralai/Mixtral-8x7B-Instruct-v0.1","apac.amazon.nova-lite-v1:0","apac.amazon.nova-micro-v1:0","apac.amazon.nova-pro-v1:0","apac.anthropic.claude-3-5-sonnet-20240620-v1:0","apac.anthropic.claude-3-5-sonnet-20241022-v2:0","apac.anthropic.claude-3-haiku-20240307-v1:0","apac.anthropic.claude-haiku-4-5-20251001-v1:0","apac.anthropic.claude-3-sonnet-20240229-v1:0","apac.anthropic.claude-sonnet-4-20250514-v1:0","assemblyai/best","assemblyai/nano","au.anthropic.claude-sonnet-4-5-20250929-v1:0","azure/ada","azure/codex-mini","azure/command-r-plus","azure_ai/claude-haiku-4-5","azure_ai/claude-opus-4-1","azure_ai/claude-sonnet-4-5","azure/computer-use-preview","azure/container","azure/eu/gpt-4o-2024-08-06","azure/eu/gpt-4o-2024-11-20","azure/eu/gpt-4o-mini-2024-07-18","azure/eu/gpt-4o-mini-realtime-preview-2024-12-17","azure/eu/gpt-4o-realtime-preview-2024-10-01","azure/eu/gpt-4o-realtime-preview-2024-12-17","azure/eu/gpt-5-2025-08-07","azure/eu/gpt-5-mini-2025-08-07","azure/eu/gpt-5.1","azure/eu/gpt-5.1-chat","azure/eu/gpt-5.1-codex","azure/eu/gpt-5.1-codex-mini","azure/eu/gpt-5-nano-2025-08-07","azure/eu/o1-2024-12-17","azure/eu/o1-mini-2024-09-12","azure/eu/o1-preview-2024-09-12","azure/eu/o3-mini-2025-01-31","azure/global-standard/gpt-4o-2024-08-06","azure/global-standard/gpt-4o-2024-11-20","azure/global-standard/gpt-4o-mini","azure/global/gpt-4o-2024-08-06","azure/global/gpt-4o-2024-11-20","azure/global/gpt-5.1","azure/global/gpt-5.1-chat","azure/global/gpt-5.1-codex","azure/global/gpt-5.1-codex-mini","azure/gpt-3.5-turbo","azure/gpt-3.5-turbo-0125","azure/gpt-3.5-turbo-instruct-0914","azure/gpt-35-turbo","azure/gpt-35-turbo-0125","azure/gpt-35-turbo-0301","azure/gpt-35-turbo-0613","azure/gpt-35-turbo-1106","azure/gpt-35-turbo-16k","azure/gpt-35-turbo-16k-0613","azure/gpt-35-turbo-instruct","azure/gpt-35-turbo-instruct-0914","azure/gpt-4","azure/gpt-4-0125-preview","azure/gpt-4-0613","azure/gpt-4-1106-preview","azure/gpt-4-32k","azure/gpt-4-32k-0613","azure/gpt-4-turbo","azure/gpt-4-turbo-2024-04-09","azure/gpt-4-turbo-vision-preview","azure/gpt-4.1","azure/gpt-4.1-2025-04-14","azure/gpt-4.1-mini","azure/gpt-4.1-mini-2025-04-14","azure/gpt-4.1-nano","azure/gpt-4.1-nano-2025-04-14","azure/gpt-4.5-preview","azure/gpt-4o","azure/gpt-4o-2024-05-13","azure/gpt-4o-2024-08-06","azure/gpt-4o-2024-11-20","azure/gpt-audio-2025-08-28","azure/gpt-audio-mini-2025-10-06","azure/gpt-4o-audio-preview-2024-12-17","azure/gpt-4o-mini","azure/gpt-4o-mini-2024-07-18","azure/gpt-4o-mini-audio-preview-2024-12-17","azure/gpt-4o-mini-realtime-preview-2024-12-17","azure/gpt-realtime-2025-08-28","azure/gpt-realtime-mini-2025-10-06","azure/gpt-4o-mini-transcribe","azure/gpt-4o-mini-tts","azure/gpt-4o-realtime-preview-2024-10-01","azure/gpt-4o-realtime-preview-2024-12-17","azure/gpt-4o-transcribe","azure/gpt-4o-transcribe-diarize","azure/gpt-5.1-2025-11-13","azure/gpt-5.1-chat-2025-11-13","azure/gpt-5.1-codex-2025-11-13","azure/gpt-5.1-codex-mini-2025-11-13","azure/gpt-5","azure/gpt-5-2025-08-07","azure/gpt-5-chat","azure/gpt-5-chat-latest","azure/gpt-5-codex","azure/gpt-5-mini","azure/gpt-5-mini-2025-08-07","azure/gpt-5-nano","azure/gpt-5-nano-2025-08-07","azure/gpt-5-pro","azure/gpt-5.1","azure/gpt-5.1-chat","azure/gpt-5.1-codex","azure/gpt-5.1-codex-max","azure/gpt-5.1-codex-mini","azure/gpt-5.2","azure/gpt-5.2-2025-12-11","azure/gpt-5.2-chat-2025-12-11","azure/gpt-5.2-pro","azure/gpt-5.2-pro-2025-12-11","azure/gpt-image-1","azure/hd/1024-x-1024/dall-e-3","azure/hd/1024-x-1792/dall-e-3","azure/hd/1792-x-1024/dall-e-3","azure/high/1024-x-1024/gpt-image-1","azure/high/1024-x-1536/gpt-image-1","azure/high/1536-x-1024/gpt-image-1","azure/low/1024-x-1024/gpt-image-1","azure/low/1024-x-1536/gpt-image-1","azure/low/1536-x-1024/gpt-image-1","azure/medium/1024-x-1024/gpt-image-1","azure/medium/1024-x-1536/gpt-image-1","azure/medium/1536-x-1024/gpt-image-1","azure/gpt-image-1-mini","azure/low/1024-x-1024/gpt-image-1-mini","azure/low/1024-x-1536/gpt-image-1-mini","azure/low/1536-x-1024/gpt-image-1-mini","azure/medium/1024-x-1024/gpt-image-1-mini","azure/medium/1024-x-1536/gpt-image-1-mini","azure/medium/1536-x-1024/gpt-image-1-mini","azure/high/1024-x-1024/gpt-image-1-mini","azure/high/1024-x-1536/gpt-image-1-mini","azure/high/1536-x-1024/gpt-image-1-mini","azure/mistral-large-2402","azure/mistral-large-latest","azure/o1","azure/o1-2024-12-17","azure/o1-mini","azure/o1-mini-2024-09-12","azure/o1-preview","azure/o1-preview-2024-09-12","azure/o3","azure/o3-2025-04-16","azure/o3-deep-research","azure/o3-mini","azure/o3-mini-2025-01-31","azure/o3-pro","azure/o3-pro-2025-06-10","azure/o4-mini","azure/o4-mini-2025-04-16","azure/standard/1024-x-1024/dall-e-2","azure/standard/1024-x-1024/dall-e-3","azure/standard/1024-x-1792/dall-e-3","azure/standard/1792-x-1024/dall-e-3","azure/text-embedding-3-large","azure/text-embedding-3-small","azure/text-embedding-ada-002","azure/speech/azure-tts","azure/speech/azure-tts-hd","azure/tts-1","azure/tts-1-hd","azure/us/gpt-4.1-2025-04-14","azure/us/gpt-4.1-mini-2025-04-14","azure/us/gpt-4.1-nano-2025-04-14","azure/us/gpt-4o-2024-08-06","azure/us/gpt-4o-2024-11-20","azure/us/gpt-4o-mini-2024-07-18","azure/us/gpt-4o-mini-realtime-preview-2024-12-17","azure/us/gpt-4o-realtime-preview-2024-10-01","azure/us/gpt-4o-realtime-preview-2024-12-17","azure/us/gpt-5-2025-08-07","azure/us/gpt-5-mini-2025-08-07","azure/us/gpt-5-nano-2025-08-07","azure/us/gpt-5.1","azure/us/gpt-5.1-chat","azure/us/gpt-5.1-codex","azure/us/gpt-5.1-codex-mini","azure/us/o1-2024-12-17","azure/us/o1-mini-2024-09-12","azure/us/o1-preview-2024-09-12","azure/us/o3-2025-04-16","azure/us/o3-mini-2025-01-31","azure/us/o4-mini-2025-04-16","azure/whisper-1","azure_ai/Cohere-embed-v3-english","azure_ai/Cohere-embed-v3-multilingual","azure_ai/FLUX-1.1-pro","azure_ai/FLUX.1-Kontext-pro","azure_ai/Llama-3.2-11B-Vision-Instruct","azure_ai/Llama-3.2-90B-Vision-Instruct","azure_ai/Llama-3.3-70B-Instruct","azure_ai/Llama-4-Maverick-17B-128E-Instruct-FP8","azure_ai/Llama-4-Scout-17B-16E-Instruct","azure_ai/Meta-Llama-3-70B-Instruct","azure_ai/Meta-Llama-3.1-405B-Instruct","azure_ai/Meta-Llama-3.1-70B-Instruct","azure_ai/Meta-Llama-3.1-8B-Instruct","azure_ai/Phi-3-medium-128k-instruct","azure_ai/Phi-3-medium-4k-instruct","azure_ai/Phi-3-mini-128k-instruct","azure_ai/Phi-3-mini-4k-instruct","azure_ai/Phi-3-small-128k-instruct","azure_ai/Phi-3-small-8k-instruct","azure_ai/Phi-3.5-MoE-instruct","azure_ai/Phi-3.5-mini-instruct","azure_ai/Phi-3.5-vision-instruct","azure_ai/Phi-4","azure_ai/Phi-4-mini-instruct","azure_ai/Phi-4-multimodal-instruct","azure_ai/Phi-4-mini-reasoning","azure_ai/Phi-4-reasoning","azure_ai/mistral-document-ai-2505","azure_ai/doc-intelligence/prebuilt-read","azure_ai/doc-intelligence/prebuilt-layout","azure_ai/doc-intelligence/prebuilt-document","azure_ai/MAI-DS-R1","azure_ai/cohere-rerank-v3-english","azure_ai/cohere-rerank-v3-multilingual","azure_ai/cohere-rerank-v3.5","azure_ai/cohere-rerank-v4.0-pro","azure_ai/cohere-rerank-v4.0-fast","azure_ai/deepseek-r1","azure_ai/deepseek-v3","azure_ai/deepseek-v3-0324","azure_ai/embed-v-4-0","azure_ai/global/grok-3","azure_ai/global/grok-3-mini","azure_ai/grok-3","azure_ai/grok-3-mini","azure_ai/grok-4","azure_ai/grok-4-fast-non-reasoning","azure_ai/grok-4-fast-reasoning","azure_ai/grok-code-fast-1","azure_ai/jais-30b-chat","azure_ai/jamba-instruct","azure_ai/ministral-3b","azure_ai/mistral-large","azure_ai/mistral-large-2407","azure_ai/mistral-large-latest","azure_ai/mistral-large-3","azure_ai/mistral-medium-2505","azure_ai/mistral-nemo","azure_ai/mistral-small","azure_ai/mistral-small-2503","babbage-002","bedrock/*/1-month-commitment/cohere.command-light-text-v14","bedrock/*/1-month-commitment/cohere.command-text-v14","bedrock/*/6-month-commitment/cohere.command-light-text-v14","bedrock/*/6-month-commitment/cohere.command-text-v14","bedrock/ap-northeast-1/1-month-commitment/anthropic.claude-instant-v1","bedrock/ap-northeast-1/1-month-commitment/anthropic.claude-v1","bedrock/ap-northeast-1/1-month-commitment/anthropic.claude-v2:1","bedrock/ap-northeast-1/6-month-commitment/anthropic.claude-instant-v1","bedrock/ap-northeast-1/6-month-commitment/anthropic.claude-v1","bedrock/ap-northeast-1/6-month-commitment/anthropic.claude-v2:1","bedrock/ap-northeast-1/anthropic.claude-instant-v1","bedrock/ap-northeast-1/anthropic.claude-v1","bedrock/ap-northeast-1/anthropic.claude-v2:1","bedrock/ap-south-1/meta.llama3-70b-instruct-v1:0","bedrock/ap-south-1/meta.llama3-8b-instruct-v1:0","bedrock/ca-central-1/meta.llama3-70b-instruct-v1:0","bedrock/ca-central-1/meta.llama3-8b-instruct-v1:0","bedrock/eu-central-1/1-month-commitment/anthropic.claude-instant-v1","bedrock/eu-central-1/1-month-commitment/anthropic.claude-v1","bedrock/eu-central-1/1-month-commitment/anthropic.claude-v2:1","bedrock/eu-central-1/6-month-commitment/anthropic.claude-instant-v1","bedrock/eu-central-1/6-month-commitment/anthropic.claude-v1","bedrock/eu-central-1/6-month-commitment/anthropic.claude-v2:1","bedrock/eu-central-1/anthropic.claude-instant-v1","bedrock/eu-central-1/anthropic.claude-v1","bedrock/eu-central-1/anthropic.claude-v2:1","bedrock/eu-west-1/meta.llama3-70b-instruct-v1:0","bedrock/eu-west-1/meta.llama3-8b-instruct-v1:0","bedrock/eu-west-2/meta.llama3-70b-instruct-v1:0","bedrock/eu-west-2/meta.llama3-8b-instruct-v1:0","bedrock/eu-west-3/mistral.mistral-7b-instruct-v0:2","bedrock/eu-west-3/mistral.mistral-large-2402-v1:0","bedrock/eu-west-3/mistral.mixtral-8x7b-instruct-v0:1","bedrock/invoke/anthropic.claude-3-5-sonnet-20240620-v1:0","bedrock/sa-east-1/meta.llama3-70b-instruct-v1:0","bedrock/sa-east-1/meta.llama3-8b-instruct-v1:0","bedrock/us-east-1/1-month-commitment/anthropic.claude-instant-v1","bedrock/us-east-1/1-month-commitment/anthropic.claude-v1","bedrock/us-east-1/1-month-commitment/anthropic.claude-v2:1","bedrock/us-east-1/6-month-commitment/anthropic.claude-instant-v1","bedrock/us-east-1/6-month-commitment/anthropic.claude-v1","bedrock/us-east-1/6-month-commitment/anthropic.claude-v2:1","bedrock/us-east-1/anthropic.claude-instant-v1","bedrock/us-east-1/anthropic.claude-v1","bedrock/us-east-1/anthropic.claude-v2:1","bedrock/us-east-1/meta.llama3-70b-instruct-v1:0","bedrock/us-east-1/meta.llama3-8b-instruct-v1:0","bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2","bedrock/us-east-1/mistral.mistral-large-2402-v1:0","bedrock/us-east-1/mistral.mixtral-8x7b-instruct-v0:1","bedrock/us-gov-east-1/amazon.nova-pro-v1:0","bedrock/us-gov-east-1/amazon.titan-embed-text-v1","bedrock/us-gov-east-1/amazon.titan-embed-text-v2:0","bedrock/us-gov-east-1/amazon.titan-text-express-v1","bedrock/us-gov-east-1/amazon.titan-text-lite-v1","bedrock/us-gov-east-1/amazon.titan-text-premier-v1:0","bedrock/us-gov-east-1/anthropic.claude-3-5-sonnet-20240620-v1:0","bedrock/us-gov-east-1/anthropic.claude-3-haiku-20240307-v1:0","bedrock/us-gov-east-1/claude-sonnet-4-5-20250929-v1:0","bedrock/us-gov-east-1/meta.llama3-70b-instruct-v1:0","bedrock/us-gov-east-1/meta.llama3-8b-instruct-v1:0","bedrock/us-gov-west-1/amazon.nova-pro-v1:0","bedrock/us-gov-west-1/amazon.titan-embed-text-v1","bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0","bedrock/us-gov-west-1/amazon.titan-text-express-v1","bedrock/us-gov-west-1/amazon.titan-text-lite-v1","bedrock/us-gov-west-1/amazon.titan-text-premier-v1:0","bedrock/us-gov-west-1/anthropic.claude-3-7-sonnet-20250219-v1:0","bedrock/us-gov-west-1/anthropic.claude-3-5-sonnet-20240620-v1:0","bedrock/us-gov-west-1/anthropic.claude-3-haiku-20240307-v1:0","bedrock/us-gov-west-1/claude-sonnet-4-5-20250929-v1:0","bedrock/us-gov-west-1/meta.llama3-70b-instruct-v1:0","bedrock/us-gov-west-1/meta.llama3-8b-instruct-v1:0","bedrock/us-west-1/meta.llama3-70b-instruct-v1:0","bedrock/us-west-1/meta.llama3-8b-instruct-v1:0","bedrock/us-west-2/1-month-commitment/anthropic.claude-instant-v1","bedrock/us-west-2/1-month-commitment/anthropic.claude-v1","bedrock/us-west-2/1-month-commitment/anthropic.claude-v2:1","bedrock/us-west-2/6-month-commitment/anthropic.claude-instant-v1","bedrock/us-west-2/6-month-commitment/anthropic.claude-v1","bedrock/us-west-2/6-month-commitment/anthropic.claude-v2:1","bedrock/us-west-2/anthropic.claude-instant-v1","bedrock/us-west-2/anthropic.claude-v1","bedrock/us-west-2/anthropic.claude-v2:1","bedrock/us-west-2/mistral.mistral-7b-instruct-v0:2","bedrock/us-west-2/mistral.mistral-large-2402-v1:0","bedrock/us-west-2/mistral.mixtral-8x7b-instruct-v0:1","bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0","cerebras/llama-3.3-70b","cerebras/llama3.1-70b","cerebras/llama3.1-8b","cerebras/gpt-oss-120b","cerebras/qwen-3-32b","cerebras/zai-glm-4.6","chat-bison","chat-bison-32k","chat-bison-32k@002","chat-bison@001","chat-bison@002","chatdolphin","chatgpt-4o-latest","claude-3-5-haiku-20241022","claude-3-5-haiku-latest","claude-haiku-4-5-20251001","claude-haiku-4-5","claude-3-5-sonnet-20240620","claude-3-5-sonnet-20241022","claude-3-5-sonnet-latest","claude-3-7-sonnet-20250219","claude-3-7-sonnet-latest","claude-3-haiku-20240307","claude-3-opus-20240229","claude-3-opus-latest","claude-4-opus-20250514","claude-4-sonnet-20250514","claude-sonnet-4-5","claude-sonnet-4-5-20250929","claude-sonnet-4-5-20250929-v1:0","claude-opus-4-1","claude-opus-4-1-20250805","claude-opus-4-20250514","claude-opus-4-5-20251101","claude-opus-4-5","claude-sonnet-4-20250514","cloudflare/@cf/meta/llama-2-7b-chat-fp16","cloudflare/@cf/meta/llama-2-7b-chat-int8","cloudflare/@cf/mistral/mistral-7b-instruct-v0.1","cloudflare/@hf/thebloke/codellama-7b-instruct-awq","code-bison","code-bison-32k@002","code-bison32k","code-bison@001","code-bison@002","code-gecko","code-gecko-latest","code-gecko@001","code-gecko@002","codechat-bison","codechat-bison-32k","codechat-bison-32k@002","codechat-bison@001","codechat-bison@002","codechat-bison@latest","codestral/codestral-2405","codestral/codestral-latest","codex-mini-latest","cohere.command-light-text-v14","cohere.command-r-plus-v1:0","cohere.command-r-v1:0","cohere.command-text-v14","cohere.embed-english-v3","cohere.embed-multilingual-v3","cohere.embed-v4:0","cohere/embed-v4.0","cohere.rerank-v3-5:0","command","command-a-03-2025","command-light","command-nightly","command-r","command-r-08-2024","command-r-plus","command-r-plus-08-2024","command-r7b-12-2024","computer-use-preview","deepseek-chat","deepseek-reasoner","dashscope/qwen-coder","dashscope/qwen-flash","dashscope/qwen-flash-2025-07-28","dashscope/qwen-max","dashscope/qwen-plus","dashscope/qwen-plus-2025-01-25","dashscope/qwen-plus-2025-04-28","dashscope/qwen-plus-2025-07-14","dashscope/qwen-plus-2025-07-28","dashscope/qwen-plus-2025-09-11","dashscope/qwen-plus-latest","dashscope/qwen-turbo","dashscope/qwen-turbo-2024-11-01","dashscope/qwen-turbo-2025-04-28","dashscope/qwen-turbo-latest","dashscope/qwen3-30b-a3b","dashscope/qwen3-coder-flash","dashscope/qwen3-coder-flash-2025-07-28","dashscope/qwen3-coder-plus","dashscope/qwen3-coder-plus-2025-07-22","dashscope/qwen3-max-preview","dashscope/qwq-plus","databricks/databricks-bge-large-en","databricks/databricks-claude-3-7-sonnet","databricks/databricks-claude-haiku-4-5","databricks/databricks-claude-opus-4","databricks/databricks-claude-opus-4-1","databricks/databricks-claude-opus-4-5","databricks/databricks-claude-sonnet-4","databricks/databricks-claude-sonnet-4-1","databricks/databricks-claude-sonnet-4-5","databricks/databricks-gemini-2-5-flash","databricks/databricks-gemini-2-5-pro","databricks/databricks-gemma-3-12b","databricks/databricks-gpt-5","databricks/databricks-gpt-5-1","databricks/databricks-gpt-5-mini","databricks/databricks-gpt-5-nano","databricks/databricks-gpt-oss-120b","databricks/databricks-gpt-oss-20b","databricks/databricks-gte-large-en","databricks/databricks-llama-2-70b-chat","databricks/databricks-llama-4-maverick","databricks/databricks-meta-llama-3-1-405b-instruct","databricks/databricks-meta-llama-3-1-8b-instruct","databricks/databricks-meta-llama-3-3-70b-instruct","databricks/databricks-meta-llama-3-70b-instruct","databricks/databricks-mixtral-8x7b-instruct","databricks/databricks-mpt-30b-instruct","databricks/databricks-mpt-7b-instruct","dataforseo/search","davinci-002","deepgram/base","deepgram/base-conversationalai","deepgram/base-finance","deepgram/base-general","deepgram/base-meeting","deepgram/base-phonecall","deepgram/base-video","deepgram/base-voicemail","deepgram/enhanced","deepgram/enhanced-finance","deepgram/enhanced-general","deepgram/enhanced-meeting","deepgram/enhanced-phonecall","deepgram/nova","deepgram/nova-2","deepgram/nova-2-atc","deepgram/nova-2-automotive","deepgram/nova-2-conversationalai","deepgram/nova-2-drivethru","deepgram/nova-2-finance","deepgram/nova-2-general","deepgram/nova-2-meeting","deepgram/nova-2-phonecall","deepgram/nova-2-video","deepgram/nova-2-voicemail","deepgram/nova-3","deepgram/nova-3-general","deepgram/nova-3-medical","deepgram/nova-general","deepgram/nova-phonecall","deepgram/whisper","deepgram/whisper-base","deepgram/whisper-large","deepgram/whisper-medium","deepgram/whisper-small","deepgram/whisper-tiny","deepinfra/Gryphe/MythoMax-L2-13b","deepinfra/NousResearch/Hermes-3-Llama-3.1-405B","deepinfra/NousResearch/Hermes-3-Llama-3.1-70B","deepinfra/Qwen/QwQ-32B","deepinfra/Qwen/Qwen2.5-72B-Instruct","deepinfra/Qwen/Qwen2.5-7B-Instruct","deepinfra/Qwen/Qwen2.5-VL-32B-Instruct","deepinfra/Qwen/Qwen3-14B","deepinfra/Qwen/Qwen3-235B-A22B","deepinfra/Qwen/Qwen3-235B-A22B-Instruct-2507","deepinfra/Qwen/Qwen3-235B-A22B-Thinking-2507","deepinfra/Qwen/Qwen3-30B-A3B","deepinfra/Qwen/Qwen3-32B","deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct","deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo","deepinfra/Qwen/Qwen3-Next-80B-A3B-Instruct","deepinfra/Qwen/Qwen3-Next-80B-A3B-Thinking","deepinfra/Sao10K/L3-8B-Lunaris-v1-Turbo","deepinfra/Sao10K/L3.1-70B-Euryale-v2.2","deepinfra/Sao10K/L3.3-70B-Euryale-v2.3","deepinfra/allenai/olmOCR-7B-0725-FP8","deepinfra/anthropic/claude-3-7-sonnet-latest","deepinfra/anthropic/claude-4-opus","deepinfra/anthropic/claude-4-sonnet","deepinfra/deepseek-ai/DeepSeek-R1","deepinfra/deepseek-ai/DeepSeek-R1-0528","deepinfra/deepseek-ai/DeepSeek-R1-0528-Turbo","deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B","deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B","deepinfra/deepseek-ai/DeepSeek-R1-Turbo","deepinfra/deepseek-ai/DeepSeek-V3","deepinfra/deepseek-ai/DeepSeek-V3-0324","deepinfra/deepseek-ai/DeepSeek-V3.1","deepinfra/deepseek-ai/DeepSeek-V3.1-Terminus","deepinfra/google/gemini-2.0-flash-001","deepinfra/google/gemini-2.5-flash","deepinfra/google/gemini-2.5-pro","deepinfra/google/gemma-3-12b-it","deepinfra/google/gemma-3-27b-it","deepinfra/google/gemma-3-4b-it","deepinfra/meta-llama/Llama-3.2-11B-Vision-Instruct","deepinfra/meta-llama/Llama-3.2-3B-Instruct","deepinfra/meta-llama/Llama-3.3-70B-Instruct","deepinfra/meta-llama/Llama-3.3-70B-Instruct-Turbo","deepinfra/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8","deepinfra/meta-llama/Llama-4-Scout-17B-16E-Instruct","deepinfra/meta-llama/Llama-Guard-3-8B","deepinfra/meta-llama/Llama-Guard-4-12B","deepinfra/meta-llama/Meta-Llama-3-8B-Instruct","deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct","deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo","deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct","deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo","deepinfra/microsoft/WizardLM-2-8x22B","deepinfra/microsoft/phi-4","deepinfra/mistralai/Mistral-Nemo-Instruct-2407","deepinfra/mistralai/Mistral-Small-24B-Instruct-2501","deepinfra/mistralai/Mistral-Small-3.2-24B-Instruct-2506","deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1","deepinfra/moonshotai/Kimi-K2-Instruct","deepinfra/moonshotai/Kimi-K2-Instruct-0905","deepinfra/nvidia/Llama-3.1-Nemotron-70B-Instruct","deepinfra/nvidia/Llama-3.3-Nemotron-Super-49B-v1.5","deepinfra/nvidia/NVIDIA-Nemotron-Nano-9B-v2","deepinfra/openai/gpt-oss-120b","deepinfra/openai/gpt-oss-20b","deepinfra/zai-org/GLM-4.5","deepseek/deepseek-chat","deepseek/deepseek-coder","deepseek/deepseek-r1","deepseek/deepseek-reasoner","deepseek/deepseek-v3","deepseek/deepseek-v3.2","deepseek.v3-v1:0","dolphin","doubao-embedding","doubao-embedding-large","doubao-embedding-large-text-240915","doubao-embedding-large-text-250515","doubao-embedding-text-240715","exa_ai/search","firecrawl/search","perplexity/search","searxng/search","elevenlabs/scribe_v1","elevenlabs/scribe_v1_experimental","embed-english-light-v2.0","embed-english-light-v3.0","embed-english-v2.0","embed-english-v3.0","embed-multilingual-v2.0","embed-multilingual-v3.0","embed-multilingual-light-v3.0","eu.amazon.nova-lite-v1:0","eu.amazon.nova-micro-v1:0","eu.amazon.nova-pro-v1:0","eu.anthropic.claude-3-5-haiku-20241022-v1:0","eu.anthropic.claude-haiku-4-5-20251001-v1:0","eu.anthropic.claude-3-5-sonnet-20240620-v1:0","eu.anthropic.claude-3-5-sonnet-20241022-v2:0","eu.anthropic.claude-3-7-sonnet-20250219-v1:0","eu.anthropic.claude-3-haiku-20240307-v1:0","eu.anthropic.claude-3-opus-20240229-v1:0","eu.anthropic.claude-3-sonnet-20240229-v1:0","eu.anthropic.claude-opus-4-1-20250805-v1:0","eu.anthropic.claude-opus-4-20250514-v1:0","eu.anthropic.claude-sonnet-4-20250514-v1:0","eu.anthropic.claude-sonnet-4-5-20250929-v1:0","eu.meta.llama3-2-1b-instruct-v1:0","eu.meta.llama3-2-3b-instruct-v1:0","eu.mistral.pixtral-large-2502-v1:0","fal_ai/bria/text-to-image/3.2","fal_ai/fal-ai/flux-pro/v1.1","fal_ai/fal-ai/flux-pro/v1.1-ultra","fal_ai/fal-ai/flux/schnell","fal_ai/fal-ai/bytedance/seedream/v3/text-to-image","fal_ai/fal-ai/bytedance/dreamina/v3.1/text-to-image","fal_ai/fal-ai/ideogram/v3","fal_ai/fal-ai/imagen4/preview","fal_ai/fal-ai/imagen4/preview/fast","fal_ai/fal-ai/imagen4/preview/ultra","fal_ai/fal-ai/recraft/v3/text-to-image","fal_ai/fal-ai/stable-diffusion-v35-medium","featherless_ai/featherless-ai/Qwerky-72B","featherless_ai/featherless-ai/Qwerky-QwQ-32B","fireworks-ai-4.1b-to-16b","fireworks-ai-56b-to-176b","fireworks-ai-above-16b","fireworks-ai-default","fireworks-ai-embedding-150m-to-350m","fireworks-ai-embedding-up-to-150m","fireworks-ai-moe-up-to-56b","fireworks-ai-up-to-4b","fireworks_ai/WhereIsAI/UAE-Large-V1","fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-instruct","fireworks_ai/accounts/fireworks/models/deepseek-r1","fireworks_ai/accounts/fireworks/models/deepseek-r1-0528","fireworks_ai/accounts/fireworks/models/deepseek-r1-basic","fireworks_ai/accounts/fireworks/models/deepseek-v3","fireworks_ai/accounts/fireworks/models/deepseek-v3-0324","fireworks_ai/accounts/fireworks/models/deepseek-v3p1","fireworks_ai/accounts/fireworks/models/deepseek-v3p1-terminus","fireworks_ai/accounts/fireworks/models/deepseek-v3p2","fireworks_ai/accounts/fireworks/models/firefunction-v2","fireworks_ai/accounts/fireworks/models/glm-4p5","fireworks_ai/accounts/fireworks/models/glm-4p5-air","fireworks_ai/accounts/fireworks/models/glm-4p6","fireworks_ai/accounts/fireworks/models/gpt-oss-120b","fireworks_ai/accounts/fireworks/models/gpt-oss-20b","fireworks_ai/accounts/fireworks/models/kimi-k2-instruct","fireworks_ai/accounts/fireworks/models/kimi-k2-instruct-0905","fireworks_ai/accounts/fireworks/models/kimi-k2-thinking","fireworks_ai/accounts/fireworks/models/llama-v3p1-405b-instruct","fireworks_ai/accounts/fireworks/models/llama-v3p1-8b-instruct","fireworks_ai/accounts/fireworks/models/llama-v3p2-11b-vision-instruct","fireworks_ai/accounts/fireworks/models/llama-v3p2-1b-instruct","fireworks_ai/accounts/fireworks/models/llama-v3p2-3b-instruct","fireworks_ai/accounts/fireworks/models/llama-v3p2-90b-vision-instruct","fireworks_ai/accounts/fireworks/models/llama4-maverick-instruct-basic","fireworks_ai/accounts/fireworks/models/llama4-scout-instruct-basic","fireworks_ai/accounts/fireworks/models/mixtral-8x22b-instruct-hf","fireworks_ai/accounts/fireworks/models/qwen2-72b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct","fireworks_ai/accounts/fireworks/models/yi-large","fireworks_ai/nomic-ai/nomic-embed-text-v1","fireworks_ai/nomic-ai/nomic-embed-text-v1.5","fireworks_ai/thenlper/gte-base","fireworks_ai/thenlper/gte-large","friendliai/meta-llama-3.1-70b-instruct","friendliai/meta-llama-3.1-8b-instruct","ft:babbage-002","ft:davinci-002","ft:gpt-3.5-turbo","ft:gpt-3.5-turbo-0125","ft:gpt-3.5-turbo-0613","ft:gpt-3.5-turbo-1106","ft:gpt-4-0613","ft:gpt-4o-2024-08-06","ft:gpt-4o-2024-11-20","ft:gpt-4o-mini-2024-07-18","ft:gpt-4.1-2025-04-14","ft:gpt-4.1-mini-2025-04-14","ft:gpt-4.1-nano-2025-04-14","ft:o4-mini-2025-04-16","gemini-1.0-pro","gemini-1.0-pro-001","gemini-1.0-pro-002","gemini-1.0-pro-vision","gemini-1.0-pro-vision-001","gemini-1.0-ultra","gemini-1.0-ultra-001","gemini-1.5-flash","gemini-1.5-flash-001","gemini-1.5-flash-002","gemini-1.5-flash-exp-0827","gemini-1.5-flash-preview-0514","gemini-1.5-pro","gemini-1.5-pro-001","gemini-1.5-pro-002","gemini-1.5-pro-preview-0215","gemini-1.5-pro-preview-0409","gemini-1.5-pro-preview-0514","gemini-2.0-flash","gemini-2.0-flash-001","gemini-2.0-flash-exp","gemini-2.0-flash-lite","gemini-2.0-flash-lite-001","gemini-2.0-flash-live-preview-04-09","gemini-2.0-flash-preview-image-generation","gemini-2.0-flash-thinking-exp","gemini-2.0-flash-thinking-exp-01-21","gemini-2.0-pro-exp-02-05","gemini-2.5-flash","gemini-2.5-flash-image","gemini-2.5-flash-image-preview","gemini-3-pro-image-preview","gemini-2.5-flash-lite","gemini-2.5-flash-lite-preview-09-2025","gemini-2.5-flash-preview-09-2025","gemini-live-2.5-flash-preview-native-audio-09-2025","gemini/gemini-live-2.5-flash-preview-native-audio-09-2025","gemini-2.5-flash-lite-preview-06-17","gemini-2.5-flash-preview-04-17","gemini-2.5-flash-preview-05-20","gemini-2.5-pro","gemini-3-pro-preview","vertex_ai/gemini-3-pro-preview","gemini-2.5-pro-exp-03-25","gemini-2.5-pro-preview-03-25","gemini-2.5-pro-preview-05-06","gemini-2.5-pro-preview-06-05","gemini-2.5-pro-preview-tts","gemini-embedding-001","gemini-flash-experimental","gemini-pro","gemini-pro-experimental","gemini-pro-vision","gemini/gemini-embedding-001","gemini/gemini-1.5-flash","gemini/gemini-1.5-flash-001","gemini/gemini-1.5-flash-002","gemini/gemini-1.5-flash-8b","gemini/gemini-1.5-flash-8b-exp-0827","gemini/gemini-1.5-flash-8b-exp-0924","gemini/gemini-1.5-flash-exp-0827","gemini/gemini-1.5-flash-latest","gemini/gemini-1.5-pro","gemini/gemini-1.5-pro-001","gemini/gemini-1.5-pro-002","gemini/gemini-1.5-pro-exp-0801","gemini/gemini-1.5-pro-exp-0827","gemini/gemini-1.5-pro-latest","gemini/gemini-2.0-flash","gemini/gemini-2.0-flash-001","gemini/gemini-2.0-flash-exp","gemini/gemini-2.0-flash-lite","gemini/gemini-2.0-flash-lite-preview-02-05","gemini/gemini-2.0-flash-live-001","gemini/gemini-2.0-flash-preview-image-generation","gemini/gemini-2.0-flash-thinking-exp","gemini/gemini-2.0-flash-thinking-exp-01-21","gemini/gemini-2.0-pro-exp-02-05","gemini/gemini-2.5-flash","gemini/gemini-2.5-flash-image","gemini/gemini-2.5-flash-image-preview","gemini/gemini-3-pro-image-preview","gemini/gemini-2.5-flash-lite","gemini/gemini-2.5-flash-lite-preview-09-2025","gemini/gemini-2.5-flash-preview-09-2025","gemini/gemini-flash-latest","gemini/gemini-flash-lite-latest","gemini/gemini-2.5-flash-lite-preview-06-17","gemini/gemini-2.5-flash-preview-04-17","gemini/gemini-2.5-flash-preview-05-20","gemini/gemini-2.5-flash-preview-tts","gemini/gemini-2.5-pro","gemini/gemini-2.5-computer-use-preview-10-2025","gemini/gemini-3-pro-preview","gemini/gemini-2.5-pro-exp-03-25","gemini/gemini-2.5-pro-preview-03-25","gemini/gemini-2.5-pro-preview-05-06","gemini/gemini-2.5-pro-preview-06-05","gemini/gemini-2.5-pro-preview-tts","gemini/gemini-exp-1114","gemini/gemini-exp-1206","gemini/gemini-gemma-2-27b-it","gemini/gemini-gemma-2-9b-it","gemini/gemini-pro","gemini/gemini-pro-vision","gemini/gemma-3-27b-it","gemini/imagen-3.0-fast-generate-001","gemini/imagen-3.0-generate-001","gemini/imagen-3.0-generate-002","gemini/imagen-4.0-fast-generate-001","gemini/imagen-4.0-generate-001","gemini/imagen-4.0-ultra-generate-001","gemini/learnlm-1.5-pro-experimental","gemini/veo-2.0-generate-001","gemini/veo-3.0-fast-generate-preview","gemini/veo-3.0-generate-preview","gemini/veo-3.1-fast-generate-preview","gemini/veo-3.1-generate-preview","google.gemma-3-12b-it","google.gemma-3-27b-it","google.gemma-3-4b-it","google_pse/search","global.anthropic.claude-sonnet-4-5-20250929-v1:0","global.anthropic.claude-sonnet-4-20250514-v1:0","global.anthropic.claude-haiku-4-5-20251001-v1:0","global.amazon.nova-2-lite-v1:0","gpt-3.5-turbo","gpt-3.5-turbo-0125","gpt-3.5-turbo-0301","gpt-3.5-turbo-0613","gpt-3.5-turbo-1106","gpt-3.5-turbo-16k","gpt-3.5-turbo-16k-0613","gpt-3.5-turbo-instruct","gpt-3.5-turbo-instruct-0914","gpt-4","gpt-4-0125-preview","gpt-4-0314","gpt-4-0613","gpt-4-1106-preview","gpt-4-1106-vision-preview","gpt-4-32k","gpt-4-32k-0314","gpt-4-32k-0613","gpt-4-turbo","gpt-4-turbo-2024-04-09","gpt-4-turbo-preview","gpt-4-vision-preview","gpt-4.1","gpt-4.1-2025-04-14","gpt-4.1-mini","gpt-4.1-mini-2025-04-14","gpt-4.1-nano","gpt-4.1-nano-2025-04-14","gpt-4.5-preview","gpt-4.5-preview-2025-02-27","gpt-4o","gpt-4o-2024-05-13","gpt-4o-2024-08-06","gpt-4o-2024-11-20","gpt-4o-audio-preview","gpt-4o-audio-preview-2024-10-01","gpt-4o-audio-preview-2024-12-17","gpt-4o-audio-preview-2025-06-03","gpt-4o-mini","gpt-4o-mini-2024-07-18","gpt-4o-mini-audio-preview","gpt-4o-mini-audio-preview-2024-12-17","gpt-4o-mini-realtime-preview","gpt-4o-mini-realtime-preview-2024-12-17","gpt-4o-mini-search-preview","gpt-4o-mini-search-preview-2025-03-11","gpt-4o-mini-transcribe","gpt-4o-mini-tts","gpt-4o-realtime-preview","gpt-4o-realtime-preview-2024-10-01","gpt-4o-realtime-preview-2024-12-17","gpt-4o-realtime-preview-2025-06-03","gpt-4o-search-preview","gpt-4o-search-preview-2025-03-11","gpt-4o-transcribe","gpt-5","gpt-5.1","gpt-5.1-2025-11-13","gpt-5.1-chat-latest","gpt-5.2","gpt-5.2-2025-12-11","gpt-5.2-chat-latest","gpt-5.2-pro","gpt-5.2-pro-2025-12-11","gpt-5-pro","gpt-5-pro-2025-10-06","gpt-5-2025-08-07","gpt-5-chat","gpt-5-chat-latest","gpt-5-codex","gpt-5.1-codex","gpt-5.1-codex-max","gpt-5.1-codex-mini","gpt-5-mini","gpt-5-mini-2025-08-07","gpt-5-nano","gpt-5-nano-2025-08-07","gpt-image-1","gpt-image-1-mini","gpt-realtime","gpt-realtime-mini","gpt-realtime-2025-08-28","gradient_ai/alibaba-qwen3-32b","gradient_ai/anthropic-claude-3-opus","gradient_ai/anthropic-claude-3.5-haiku","gradient_ai/anthropic-claude-3.5-sonnet","gradient_ai/anthropic-claude-3.7-sonnet","gradient_ai/deepseek-r1-distill-llama-70b","gradient_ai/llama3-8b-instruct","gradient_ai/llama3.3-70b-instruct","gradient_ai/mistral-nemo-instruct-2407","gradient_ai/openai-gpt-4o","gradient_ai/openai-gpt-4o-mini","gradient_ai/openai-o3","gradient_ai/openai-o3-mini","lemonade/Qwen3-Coder-30B-A3B-Instruct-GGUF","lemonade/gpt-oss-20b-mxfp4-GGUF","lemonade/gpt-oss-120b-mxfp-GGUF","lemonade/Gemma-3-4b-it-GGUF","lemonade/Qwen3-4B-Instruct-2507-GGUF","amazon-nova/nova-micro-v1","amazon-nova/nova-lite-v1","amazon-nova/nova-premier-v1","amazon-nova/nova-pro-v1","groq/deepseek-r1-distill-llama-70b","groq/distil-whisper-large-v3-en","groq/gemma-7b-it","groq/gemma2-9b-it","groq/llama-3.1-405b-reasoning","groq/llama-3.1-70b-versatile","groq/llama-3.1-8b-instant","groq/llama-3.2-11b-text-preview","groq/llama-3.2-11b-vision-preview","groq/llama-3.2-1b-preview","groq/llama-3.2-3b-preview","groq/llama-3.2-90b-text-preview","groq/llama-3.2-90b-vision-preview","groq/llama-3.3-70b-specdec","groq/llama-3.3-70b-versatile","groq/llama-guard-3-8b","groq/llama2-70b-4096","groq/llama3-groq-70b-8192-tool-use-preview","groq/llama3-groq-8b-8192-tool-use-preview","groq/meta-llama/llama-4-maverick-17b-128e-instruct","groq/meta-llama/llama-4-scout-17b-16e-instruct","groq/mistral-saba-24b","groq/mixtral-8x7b-32768","groq/moonshotai/kimi-k2-instruct","groq/moonshotai/kimi-k2-instruct-0905","groq/openai/gpt-oss-120b","groq/openai/gpt-oss-20b","groq/playai-tts","groq/qwen/qwen3-32b","groq/whisper-large-v3","groq/whisper-large-v3-turbo","hd/1024-x-1024/dall-e-3","hd/1024-x-1792/dall-e-3","hd/1792-x-1024/dall-e-3","heroku/claude-3-5-haiku","heroku/claude-3-5-sonnet-latest","heroku/claude-3-7-sonnet","heroku/claude-4-sonnet","high/1024-x-1024/gpt-image-1","high/1024-x-1536/gpt-image-1","high/1536-x-1024/gpt-image-1","hyperbolic/NousResearch/Hermes-3-Llama-3.1-70B","hyperbolic/Qwen/QwQ-32B","hyperbolic/Qwen/Qwen2.5-72B-Instruct","hyperbolic/Qwen/Qwen2.5-Coder-32B-Instruct","hyperbolic/Qwen/Qwen3-235B-A22B","hyperbolic/deepseek-ai/DeepSeek-R1","hyperbolic/deepseek-ai/DeepSeek-R1-0528","hyperbolic/deepseek-ai/DeepSeek-V3","hyperbolic/deepseek-ai/DeepSeek-V3-0324","hyperbolic/meta-llama/Llama-3.2-3B-Instruct","hyperbolic/meta-llama/Llama-3.3-70B-Instruct","hyperbolic/meta-llama/Meta-Llama-3-70B-Instruct","hyperbolic/meta-llama/Meta-Llama-3.1-405B-Instruct","hyperbolic/meta-llama/Meta-Llama-3.1-70B-Instruct","hyperbolic/meta-llama/Meta-Llama-3.1-8B-Instruct","hyperbolic/moonshotai/Kimi-K2-Instruct","j2-light","j2-mid","j2-ultra","jamba-1.5","jamba-1.5-large","jamba-1.5-large@001","jamba-1.5-mini","jamba-1.5-mini@001","jamba-large-1.6","jamba-large-1.7","jamba-mini-1.6","jamba-mini-1.7","jina-reranker-v2-base-multilingual","jp.anthropic.claude-sonnet-4-5-20250929-v1:0","jp.anthropic.claude-haiku-4-5-20251001-v1:0","lambda_ai/deepseek-llama3.3-70b","lambda_ai/deepseek-r1-0528","lambda_ai/deepseek-r1-671b","lambda_ai/deepseek-v3-0324","lambda_ai/hermes3-405b","lambda_ai/hermes3-70b","lambda_ai/hermes3-8b","lambda_ai/lfm-40b","lambda_ai/lfm-7b","lambda_ai/llama-4-maverick-17b-128e-instruct-fp8","lambda_ai/llama-4-scout-17b-16e-instruct","lambda_ai/llama3.1-405b-instruct-fp8","lambda_ai/llama3.1-70b-instruct-fp8","lambda_ai/llama3.1-8b-instruct","lambda_ai/llama3.1-nemotron-70b-instruct-fp8","lambda_ai/llama3.2-11b-vision-instruct","lambda_ai/llama3.2-3b-instruct","lambda_ai/llama3.3-70b-instruct-fp8","lambda_ai/qwen25-coder-32b-instruct","lambda_ai/qwen3-32b-fp8","low/1024-x-1024/gpt-image-1","low/1024-x-1536/gpt-image-1","low/1536-x-1024/gpt-image-1","luminous-base","luminous-base-control","luminous-extended","luminous-extended-control","luminous-supreme","luminous-supreme-control","max-x-max/50-steps/stability.stable-diffusion-xl-v0","max-x-max/max-steps/stability.stable-diffusion-xl-v0","medium/1024-x-1024/gpt-image-1","medium/1024-x-1536/gpt-image-1","medium/1536-x-1024/gpt-image-1","low/1024-x-1024/gpt-image-1-mini","low/1024-x-1536/gpt-image-1-mini","low/1536-x-1024/gpt-image-1-mini","medium/1024-x-1024/gpt-image-1-mini","medium/1024-x-1536/gpt-image-1-mini","medium/1536-x-1024/gpt-image-1-mini","medlm-large","medlm-medium","meta.llama2-13b-chat-v1","meta.llama2-70b-chat-v1","meta.llama3-1-405b-instruct-v1:0","meta.llama3-1-70b-instruct-v1:0","meta.llama3-1-8b-instruct-v1:0","meta.llama3-2-11b-instruct-v1:0","meta.llama3-2-1b-instruct-v1:0","meta.llama3-2-3b-instruct-v1:0","meta.llama3-2-90b-instruct-v1:0","meta.llama3-3-70b-instruct-v1:0","meta.llama3-70b-instruct-v1:0","meta.llama3-8b-instruct-v1:0","meta.llama4-maverick-17b-instruct-v1:0","meta.llama4-scout-17b-instruct-v1:0","meta_llama/Llama-3.3-70B-Instruct","meta_llama/Llama-3.3-8B-Instruct","meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8","meta_llama/Llama-4-Scout-17B-16E-Instruct-FP8","minimax.minimax-m2","mistral.magistral-small-2509","mistral.ministral-3-14b-instruct","mistral.ministral-3-3b-instruct","mistral.ministral-3-8b-instruct","mistral.mistral-7b-instruct-v0:2","mistral.mistral-large-2402-v1:0","mistral.mistral-large-2407-v1:0","mistral.mistral-large-3-675b-instruct","mistral.mistral-small-2402-v1:0","mistral.mixtral-8x7b-instruct-v0:1","mistral.voxtral-mini-3b-2507","mistral.voxtral-small-24b-2507","mistral/codestral-2405","mistral/codestral-2508","mistral/codestral-latest","mistral/codestral-mamba-latest","mistral/devstral-medium-2507","mistral/devstral-small-2505","mistral/devstral-small-2507","mistral/labs-devstral-small-2512","mistral/devstral-2512","mistral/magistral-medium-2506","mistral/magistral-medium-2509","mistral/mistral-ocr-latest","mistral/mistral-ocr-2505-completion","mistral/magistral-medium-latest","mistral/magistral-small-2506","mistral/magistral-small-latest","mistral/mistral-embed","mistral/codestral-embed","mistral/codestral-embed-2505","mistral/mistral-large-2402","mistral/mistral-large-2407","mistral/mistral-large-2411","mistral/mistral-large-latest","mistral/mistral-large-3","mistral/mistral-medium","mistral/mistral-medium-2312","mistral/mistral-medium-2505","mistral/mistral-medium-latest","mistral/mistral-small","mistral/mistral-small-latest","mistral/mistral-tiny","mistral/open-codestral-mamba","mistral/open-mistral-7b","mistral/open-mistral-nemo","mistral/open-mistral-nemo-2407","mistral/open-mixtral-8x22b","mistral/open-mixtral-8x7b","mistral/pixtral-12b-2409","mistral/pixtral-large-2411","mistral/pixtral-large-latest","moonshot.kimi-k2-thinking","moonshot/kimi-k2-0711-preview","moonshot/kimi-k2-0905-preview","moonshot/kimi-k2-turbo-preview","moonshot/kimi-latest","moonshot/kimi-latest-128k","moonshot/kimi-latest-32k","moonshot/kimi-latest-8k","moonshot/kimi-thinking-preview","moonshot/kimi-k2-thinking","moonshot/kimi-k2-thinking-turbo","moonshot/moonshot-v1-128k","moonshot/moonshot-v1-128k-0430","moonshot/moonshot-v1-128k-vision-preview","moonshot/moonshot-v1-32k","moonshot/moonshot-v1-32k-0430","moonshot/moonshot-v1-32k-vision-preview","moonshot/moonshot-v1-8k","moonshot/moonshot-v1-8k-0430","moonshot/moonshot-v1-8k-vision-preview","moonshot/moonshot-v1-auto","morph/morph-v3-fast","morph/morph-v3-large","multimodalembedding","multimodalembedding@001","nscale/Qwen/QwQ-32B","nscale/Qwen/Qwen2.5-Coder-32B-Instruct","nscale/Qwen/Qwen2.5-Coder-3B-Instruct","nscale/Qwen/Qwen2.5-Coder-7B-Instruct","nscale/black-forest-labs/FLUX.1-schnell","nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-70B","nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-8B","nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B","nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B","nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B","nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B","nscale/meta-llama/Llama-3.1-8B-Instruct","nscale/meta-llama/Llama-3.3-70B-Instruct","nscale/meta-llama/Llama-4-Scout-17B-16E-Instruct","nscale/mistralai/mixtral-8x22b-instruct-v0.1","nscale/stabilityai/stable-diffusion-xl-base-1.0","nvidia.nemotron-nano-12b-v2","nvidia.nemotron-nano-9b-v2","o1","o1-2024-12-17","o1-mini","o1-mini-2024-09-12","o1-preview","o1-preview-2024-09-12","o1-pro","o1-pro-2025-03-19","o3","o3-2025-04-16","o3-deep-research","o3-deep-research-2025-06-26","o3-mini","o3-mini-2025-01-31","o3-pro","o3-pro-2025-06-10","o4-mini","o4-mini-2025-04-16","o4-mini-deep-research","o4-mini-deep-research-2025-06-26","oci/meta.llama-3.1-405b-instruct","oci/meta.llama-3.2-90b-vision-instruct","oci/meta.llama-3.3-70b-instruct","oci/meta.llama-4-maverick-17b-128e-instruct-fp8","oci/meta.llama-4-scout-17b-16e-instruct","oci/xai.grok-3","oci/xai.grok-3-fast","oci/xai.grok-3-mini","oci/xai.grok-3-mini-fast","oci/xai.grok-4","oci/cohere.command-latest","oci/cohere.command-a-03-2025","oci/cohere.command-plus-latest","ollama/codegeex4","ollama/codegemma","ollama/codellama","ollama/deepseek-coder-v2-base","ollama/deepseek-coder-v2-instruct","ollama/deepseek-coder-v2-lite-base","ollama/deepseek-coder-v2-lite-instruct","ollama/deepseek-v3.1:671b-cloud","ollama/gpt-oss:120b-cloud","ollama/gpt-oss:20b-cloud","ollama/internlm2_5-20b-chat","ollama/llama2","ollama/llama2-uncensored","ollama/llama2:13b","ollama/llama2:70b","ollama/llama2:7b","ollama/llama3","ollama/llama3.1","ollama/llama3:70b","ollama/llama3:8b","ollama/mistral","ollama/mistral-7B-Instruct-v0.1","ollama/mistral-7B-Instruct-v0.2","ollama/mistral-large-instruct-2407","ollama/mixtral-8x22B-Instruct-v0.1","ollama/mixtral-8x7B-Instruct-v0.1","ollama/orca-mini","ollama/qwen3-coder:480b-cloud","ollama/vicuna","omni-moderation-2024-09-26","omni-moderation-latest","omni-moderation-latest-intents","openai.gpt-oss-120b-1:0","openai.gpt-oss-20b-1:0","openai.gpt-oss-safeguard-120b","openai.gpt-oss-safeguard-20b","openrouter/anthropic/claude-2","openrouter/anthropic/claude-3-5-haiku","openrouter/anthropic/claude-3-5-haiku-20241022","openrouter/anthropic/claude-3-haiku","openrouter/anthropic/claude-3-haiku-20240307","openrouter/anthropic/claude-3-opus","openrouter/anthropic/claude-3-sonnet","openrouter/anthropic/claude-3.5-sonnet","openrouter/anthropic/claude-3.5-sonnet:beta","openrouter/anthropic/claude-3.7-sonnet","openrouter/anthropic/claude-3.7-sonnet:beta","openrouter/anthropic/claude-instant-v1","openrouter/anthropic/claude-opus-4","openrouter/anthropic/claude-opus-4.1","openrouter/anthropic/claude-sonnet-4","openrouter/anthropic/claude-opus-4.5","openrouter/anthropic/claude-sonnet-4.5","openrouter/anthropic/claude-haiku-4.5","openrouter/bytedance/ui-tars-1.5-7b","openrouter/cognitivecomputations/dolphin-mixtral-8x7b","openrouter/cohere/command-r-plus","openrouter/databricks/dbrx-instruct","openrouter/deepseek/deepseek-chat","openrouter/deepseek/deepseek-chat-v3-0324","openrouter/deepseek/deepseek-chat-v3.1","openrouter/deepseek/deepseek-v3.2","openrouter/deepseek/deepseek-v3.2-exp","openrouter/deepseek/deepseek-coder","openrouter/deepseek/deepseek-r1","openrouter/deepseek/deepseek-r1-0528","openrouter/fireworks/firellava-13b","openrouter/google/gemini-2.0-flash-001","openrouter/google/gemini-2.5-flash","openrouter/google/gemini-2.5-pro","openrouter/google/gemini-3-pro-preview","openrouter/google/gemini-pro-1.5","openrouter/google/gemini-pro-vision","openrouter/google/palm-2-chat-bison","openrouter/google/palm-2-codechat-bison","openrouter/gryphe/mythomax-l2-13b","openrouter/jondurbin/airoboros-l2-70b-2.1","openrouter/mancer/weaver","openrouter/meta-llama/codellama-34b-instruct","openrouter/meta-llama/llama-2-13b-chat","openrouter/meta-llama/llama-2-70b-chat","openrouter/meta-llama/llama-3-70b-instruct","openrouter/meta-llama/llama-3-70b-instruct:nitro","openrouter/meta-llama/llama-3-8b-instruct:extended","openrouter/meta-llama/llama-3-8b-instruct:free","openrouter/microsoft/wizardlm-2-8x22b:nitro","openrouter/minimax/minimax-m2","openrouter/mistralai/mistral-7b-instruct","openrouter/mistralai/mistral-7b-instruct:free","openrouter/mistralai/mistral-large","openrouter/mistralai/mistral-small-3.1-24b-instruct","openrouter/mistralai/mistral-small-3.2-24b-instruct","openrouter/mistralai/mixtral-8x22b-instruct","openrouter/nousresearch/nous-hermes-llama2-13b","openrouter/openai/gpt-3.5-turbo","openrouter/openai/gpt-3.5-turbo-16k","openrouter/openai/gpt-4","openrouter/openai/gpt-4-vision-preview","openrouter/openai/gpt-4.1","openrouter/openai/gpt-4.1-2025-04-14","openrouter/openai/gpt-4.1-mini","openrouter/openai/gpt-4.1-mini-2025-04-14","openrouter/openai/gpt-4.1-nano","openrouter/openai/gpt-4.1-nano-2025-04-14","openrouter/openai/gpt-4o","openrouter/openai/gpt-4o-2024-05-13","openrouter/openai/gpt-5-chat","openrouter/openai/gpt-5-codex","openrouter/openai/gpt-5","openrouter/openai/gpt-5-mini","openrouter/openai/gpt-5-nano","openrouter/openai/gpt-oss-120b","openrouter/openai/gpt-oss-20b","openrouter/openai/o1","openrouter/openai/o1-mini","openrouter/openai/o1-mini-2024-09-12","openrouter/openai/o1-preview","openrouter/openai/o1-preview-2024-09-12","openrouter/openai/o3-mini","openrouter/openai/o3-mini-high","openrouter/pygmalionai/mythalion-13b","openrouter/qwen/qwen-2.5-coder-32b-instruct","openrouter/qwen/qwen-vl-plus","openrouter/qwen/qwen3-coder","openrouter/switchpoint/router","openrouter/undi95/remm-slerp-l2-13b","openrouter/x-ai/grok-4","openrouter/x-ai/grok-4-fast:free","openrouter/z-ai/glm-4.6","openrouter/z-ai/glm-4.6:exacto","ovhcloud/DeepSeek-R1-Distill-Llama-70B","ovhcloud/Llama-3.1-8B-Instruct","ovhcloud/Meta-Llama-3_1-70B-Instruct","ovhcloud/Meta-Llama-3_3-70B-Instruct","ovhcloud/Mistral-7B-Instruct-v0.3","ovhcloud/Mistral-Nemo-Instruct-2407","ovhcloud/Mistral-Small-3.2-24B-Instruct-2506","ovhcloud/Mixtral-8x7B-Instruct-v0.1","ovhcloud/Qwen2.5-Coder-32B-Instruct","ovhcloud/Qwen2.5-VL-72B-Instruct","ovhcloud/Qwen3-32B","ovhcloud/gpt-oss-120b","ovhcloud/gpt-oss-20b","ovhcloud/llava-v1.6-mistral-7b-hf","ovhcloud/mamba-codestral-7B-v0.1","palm/chat-bison","palm/chat-bison-001","palm/text-bison","palm/text-bison-001","palm/text-bison-safety-off","palm/text-bison-safety-recitation-off","parallel_ai/search","parallel_ai/search-pro","perplexity/codellama-34b-instruct","perplexity/codellama-70b-instruct","perplexity/llama-2-70b-chat","perplexity/llama-3.1-70b-instruct","perplexity/llama-3.1-8b-instruct","perplexity/llama-3.1-sonar-huge-128k-online","perplexity/llama-3.1-sonar-large-128k-chat","perplexity/llama-3.1-sonar-large-128k-online","perplexity/llama-3.1-sonar-small-128k-chat","perplexity/llama-3.1-sonar-small-128k-online","perplexity/mistral-7b-instruct","perplexity/mixtral-8x7b-instruct","perplexity/pplx-70b-chat","perplexity/pplx-70b-online","perplexity/pplx-7b-chat","perplexity/pplx-7b-online","perplexity/sonar","perplexity/sonar-deep-research","perplexity/sonar-medium-chat","perplexity/sonar-medium-online","perplexity/sonar-pro","perplexity/sonar-reasoning","perplexity/sonar-reasoning-pro","perplexity/sonar-small-chat","perplexity/sonar-small-online","publicai/swiss-ai/apertus-8b-instruct","publicai/swiss-ai/apertus-70b-instruct","publicai/aisingapore/Gemma-SEA-LION-v4-27B-IT","publicai/BSC-LT/salamandra-7b-instruct-tools-16k","publicai/BSC-LT/ALIA-40b-instruct_Q8_0","publicai/allenai/Olmo-3-7B-Instruct","publicai/aisingapore/Qwen-SEA-LION-v4-32B-IT","publicai/allenai/Olmo-3-7B-Think","publicai/allenai/Olmo-3-32B-Think","qwen.qwen3-coder-480b-a35b-v1:0","qwen.qwen3-235b-a22b-2507-v1:0","qwen.qwen3-coder-30b-a3b-v1:0","qwen.qwen3-32b-v1:0","qwen.qwen3-next-80b-a3b","qwen.qwen3-vl-235b-a22b","recraft/recraftv2","recraft/recraftv3","replicate/meta/llama-2-13b","replicate/meta/llama-2-13b-chat","replicate/meta/llama-2-70b","replicate/meta/llama-2-70b-chat","replicate/meta/llama-2-7b","replicate/meta/llama-2-7b-chat","replicate/meta/llama-3-70b","replicate/meta/llama-3-70b-instruct","replicate/meta/llama-3-8b","replicate/meta/llama-3-8b-instruct","replicate/mistralai/mistral-7b-instruct-v0.2","replicate/mistralai/mistral-7b-v0.1","replicate/mistralai/mixtral-8x7b-instruct-v0.1","rerank-english-v2.0","rerank-english-v3.0","rerank-multilingual-v2.0","rerank-multilingual-v3.0","rerank-v3.5","nvidia_nim/nvidia/nv-rerankqa-mistral-4b-v3","nvidia_nim/nvidia/llama-3_2-nv-rerankqa-1b-v2","nvidia_nim/ranking/nvidia/llama-3.2-nv-rerankqa-1b-v2","sagemaker/meta-textgeneration-llama-2-13b","sagemaker/meta-textgeneration-llama-2-13b-f","sagemaker/meta-textgeneration-llama-2-70b","sagemaker/meta-textgeneration-llama-2-70b-b-f","sagemaker/meta-textgeneration-llama-2-7b","sagemaker/meta-textgeneration-llama-2-7b-f","sambanova/DeepSeek-R1","sambanova/DeepSeek-R1-Distill-Llama-70B","sambanova/DeepSeek-V3-0324","sambanova/Llama-4-Maverick-17B-128E-Instruct","sambanova/Llama-4-Scout-17B-16E-Instruct","sambanova/Meta-Llama-3.1-405B-Instruct","sambanova/Meta-Llama-3.1-8B-Instruct","sambanova/Meta-Llama-3.2-1B-Instruct","sambanova/Meta-Llama-3.2-3B-Instruct","sambanova/Meta-Llama-3.3-70B-Instruct","sambanova/Meta-Llama-Guard-3-8B","sambanova/QwQ-32B","sambanova/Qwen2-Audio-7B-Instruct","sambanova/Qwen3-32B","sambanova/DeepSeek-V3.1","sambanova/gpt-oss-120b","snowflake/claude-3-5-sonnet","snowflake/deepseek-r1","snowflake/gemma-7b","snowflake/jamba-1.5-large","snowflake/jamba-1.5-mini","snowflake/jamba-instruct","snowflake/llama2-70b-chat","snowflake/llama3-70b","snowflake/llama3-8b","snowflake/llama3.1-405b","snowflake/llama3.1-70b","snowflake/llama3.1-8b","snowflake/llama3.2-1b","snowflake/llama3.2-3b","snowflake/llama3.3-70b","snowflake/mistral-7b","snowflake/mistral-large","snowflake/mistral-large2","snowflake/mixtral-8x7b","snowflake/reka-core","snowflake/reka-flash","snowflake/snowflake-arctic","snowflake/snowflake-llama-3.1-405b","snowflake/snowflake-llama-3.3-70b","stability/sd3","stability/sd3-large","stability/sd3-large-turbo","stability/sd3-medium","stability/sd3.5-large","stability/sd3.5-large-turbo","stability/sd3.5-medium","stability/stable-image-ultra","stability/stable-image-core","stability.sd3-5-large-v1:0","stability.sd3-large-v1:0","stability.stable-image-core-v1:0","stability.stable-image-core-v1:1","stability.stable-image-ultra-v1:0","stability.stable-image-ultra-v1:1","standard/1024-x-1024/dall-e-3","standard/1024-x-1792/dall-e-3","standard/1792-x-1024/dall-e-3","tavily/search","tavily/search-advanced","text-bison","text-bison32k","text-bison32k@002","text-bison@001","text-bison@002","text-completion-codestral/codestral-2405","text-completion-codestral/codestral-latest","text-embedding-004","text-embedding-005","text-embedding-3-large","text-embedding-3-small","text-embedding-ada-002","text-embedding-ada-002-v2","text-embedding-large-exp-03-07","text-embedding-preview-0409","text-moderation-007","text-moderation-latest","text-moderation-stable","text-multilingual-embedding-002","text-multilingual-embedding-preview-0409","text-unicorn","text-unicorn@001","textembedding-gecko","textembedding-gecko-multilingual","textembedding-gecko-multilingual@001","textembedding-gecko@001","textembedding-gecko@003","together-ai-21.1b-41b","together-ai-4.1b-8b","together-ai-41.1b-80b","together-ai-8.1b-21b","together-ai-81.1b-110b","together-ai-embedding-151m-to-350m","together-ai-embedding-up-to-150m","together_ai/baai/bge-base-en-v1.5","together_ai/BAAI/bge-base-en-v1.5","together-ai-up-to-4b","together_ai/Qwen/Qwen2.5-72B-Instruct-Turbo","together_ai/Qwen/Qwen2.5-7B-Instruct-Turbo","together_ai/Qwen/Qwen3-235B-A22B-Instruct-2507-tput","together_ai/Qwen/Qwen3-235B-A22B-Thinking-2507","together_ai/Qwen/Qwen3-235B-A22B-fp8-tput","together_ai/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8","together_ai/deepseek-ai/DeepSeek-R1","together_ai/deepseek-ai/DeepSeek-R1-0528-tput","together_ai/deepseek-ai/DeepSeek-V3","together_ai/deepseek-ai/DeepSeek-V3.1","together_ai/meta-llama/Llama-3.2-3B-Instruct-Turbo","together_ai/meta-llama/Llama-3.3-70B-Instruct-Turbo","together_ai/meta-llama/Llama-3.3-70B-Instruct-Turbo-Free","together_ai/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8","together_ai/meta-llama/Llama-4-Scout-17B-16E-Instruct","together_ai/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo","together_ai/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo","together_ai/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo","together_ai/mistralai/Mistral-7B-Instruct-v0.1","together_ai/mistralai/Mistral-Small-24B-Instruct-2501","together_ai/mistralai/Mixtral-8x7B-Instruct-v0.1","together_ai/moonshotai/Kimi-K2-Instruct","together_ai/openai/gpt-oss-120b","together_ai/openai/gpt-oss-20b","together_ai/togethercomputer/CodeLlama-34b-Instruct","together_ai/zai-org/GLM-4.5-Air-FP8","together_ai/zai-org/GLM-4.6","together_ai/moonshotai/Kimi-K2-Instruct-0905","together_ai/Qwen/Qwen3-Next-80B-A3B-Instruct","together_ai/Qwen/Qwen3-Next-80B-A3B-Thinking","tts-1","tts-1-hd","us.amazon.nova-lite-v1:0","us.amazon.nova-micro-v1:0","us.amazon.nova-premier-v1:0","us.amazon.nova-pro-v1:0","us.anthropic.claude-3-5-haiku-20241022-v1:0","us.anthropic.claude-haiku-4-5-20251001-v1:0","us.anthropic.claude-3-5-sonnet-20240620-v1:0","us.anthropic.claude-3-5-sonnet-20241022-v2:0","us.anthropic.claude-3-7-sonnet-20250219-v1:0","us.anthropic.claude-3-haiku-20240307-v1:0","us.anthropic.claude-3-opus-20240229-v1:0","us.anthropic.claude-3-sonnet-20240229-v1:0","us.anthropic.claude-opus-4-1-20250805-v1:0","us.anthropic.claude-sonnet-4-5-20250929-v1:0","au.anthropic.claude-haiku-4-5-20251001-v1:0","us.anthropic.claude-opus-4-20250514-v1:0","us.anthropic.claude-opus-4-5-20251101-v1:0","global.anthropic.claude-opus-4-5-20251101-v1:0","eu.anthropic.claude-opus-4-5-20251101-v1:0","us.anthropic.claude-sonnet-4-20250514-v1:0","us.deepseek.r1-v1:0","us.meta.llama3-1-405b-instruct-v1:0","us.meta.llama3-1-70b-instruct-v1:0","us.meta.llama3-1-8b-instruct-v1:0","us.meta.llama3-2-11b-instruct-v1:0","us.meta.llama3-2-1b-instruct-v1:0","us.meta.llama3-2-3b-instruct-v1:0","us.meta.llama3-2-90b-instruct-v1:0","us.meta.llama3-3-70b-instruct-v1:0","us.meta.llama4-maverick-17b-instruct-v1:0","us.meta.llama4-scout-17b-instruct-v1:0","us.mistral.pixtral-large-2502-v1:0","v0/v0-1.0-md","v0/v0-1.5-lg","v0/v0-1.5-md","vercel_ai_gateway/alibaba/qwen-3-14b","vercel_ai_gateway/alibaba/qwen-3-235b","vercel_ai_gateway/alibaba/qwen-3-30b","vercel_ai_gateway/alibaba/qwen-3-32b","vercel_ai_gateway/alibaba/qwen3-coder","vercel_ai_gateway/amazon/nova-lite","vercel_ai_gateway/amazon/nova-micro","vercel_ai_gateway/amazon/nova-pro","vercel_ai_gateway/amazon/titan-embed-text-v2","vercel_ai_gateway/anthropic/claude-3-haiku","vercel_ai_gateway/anthropic/claude-3-opus","vercel_ai_gateway/anthropic/claude-3.5-haiku","vercel_ai_gateway/anthropic/claude-3.5-sonnet","vercel_ai_gateway/anthropic/claude-3.7-sonnet","vercel_ai_gateway/anthropic/claude-4-opus","vercel_ai_gateway/anthropic/claude-4-sonnet","vercel_ai_gateway/cohere/command-a","vercel_ai_gateway/cohere/command-r","vercel_ai_gateway/cohere/command-r-plus","vercel_ai_gateway/cohere/embed-v4.0","vercel_ai_gateway/deepseek/deepseek-r1","vercel_ai_gateway/deepseek/deepseek-r1-distill-llama-70b","vercel_ai_gateway/deepseek/deepseek-v3","vercel_ai_gateway/google/gemini-2.0-flash","vercel_ai_gateway/google/gemini-2.0-flash-lite","vercel_ai_gateway/google/gemini-2.5-flash","vercel_ai_gateway/google/gemini-2.5-pro","vercel_ai_gateway/google/gemini-embedding-001","vercel_ai_gateway/google/gemma-2-9b","vercel_ai_gateway/google/text-embedding-005","vercel_ai_gateway/google/text-multilingual-embedding-002","vercel_ai_gateway/inception/mercury-coder-small","vercel_ai_gateway/meta/llama-3-70b","vercel_ai_gateway/meta/llama-3-8b","vercel_ai_gateway/meta/llama-3.1-70b","vercel_ai_gateway/meta/llama-3.1-8b","vercel_ai_gateway/meta/llama-3.2-11b","vercel_ai_gateway/meta/llama-3.2-1b","vercel_ai_gateway/meta/llama-3.2-3b","vercel_ai_gateway/meta/llama-3.2-90b","vercel_ai_gateway/meta/llama-3.3-70b","vercel_ai_gateway/meta/llama-4-maverick","vercel_ai_gateway/meta/llama-4-scout","vercel_ai_gateway/mistral/codestral","vercel_ai_gateway/mistral/codestral-embed","vercel_ai_gateway/mistral/devstral-small","vercel_ai_gateway/mistral/magistral-medium","vercel_ai_gateway/mistral/magistral-small","vercel_ai_gateway/mistral/ministral-3b","vercel_ai_gateway/mistral/ministral-8b","vercel_ai_gateway/mistral/mistral-embed","vercel_ai_gateway/mistral/mistral-large","vercel_ai_gateway/mistral/mistral-saba-24b","vercel_ai_gateway/mistral/mistral-small","vercel_ai_gateway/mistral/mixtral-8x22b-instruct","vercel_ai_gateway/mistral/pixtral-12b","vercel_ai_gateway/mistral/pixtral-large","vercel_ai_gateway/moonshotai/kimi-k2","vercel_ai_gateway/morph/morph-v3-fast","vercel_ai_gateway/morph/morph-v3-large","vercel_ai_gateway/openai/gpt-3.5-turbo","vercel_ai_gateway/openai/gpt-3.5-turbo-instruct","vercel_ai_gateway/openai/gpt-4-turbo","vercel_ai_gateway/openai/gpt-4.1","vercel_ai_gateway/openai/gpt-4.1-mini","vercel_ai_gateway/openai/gpt-4.1-nano","vercel_ai_gateway/openai/gpt-4o","vercel_ai_gateway/openai/gpt-4o-mini","vercel_ai_gateway/openai/o1","vercel_ai_gateway/openai/o3","vercel_ai_gateway/openai/o3-mini","vercel_ai_gateway/openai/o4-mini","vercel_ai_gateway/openai/text-embedding-3-large","vercel_ai_gateway/openai/text-embedding-3-small","vercel_ai_gateway/openai/text-embedding-ada-002","vercel_ai_gateway/perplexity/sonar","vercel_ai_gateway/perplexity/sonar-pro","vercel_ai_gateway/perplexity/sonar-reasoning","vercel_ai_gateway/perplexity/sonar-reasoning-pro","vercel_ai_gateway/vercel/v0-1.0-md","vercel_ai_gateway/vercel/v0-1.5-md","vercel_ai_gateway/xai/grok-2","vercel_ai_gateway/xai/grok-2-vision","vercel_ai_gateway/xai/grok-3","vercel_ai_gateway/xai/grok-3-fast","vercel_ai_gateway/xai/grok-3-mini","vercel_ai_gateway/xai/grok-3-mini-fast","vercel_ai_gateway/xai/grok-4","vercel_ai_gateway/zai/glm-4.5","vercel_ai_gateway/zai/glm-4.5-air","vercel_ai_gateway/zai/glm-4.6","vertex_ai/chirp","vertex_ai/claude-3-5-haiku","vertex_ai/claude-3-5-haiku@20241022","vertex_ai/claude-haiku-4-5@20251001","vertex_ai/claude-3-5-sonnet","vertex_ai/claude-3-5-sonnet-v2","vertex_ai/claude-3-5-sonnet-v2@20241022","vertex_ai/claude-3-5-sonnet@20240620","vertex_ai/claude-3-7-sonnet@20250219","vertex_ai/claude-3-haiku","vertex_ai/claude-3-haiku@20240307","vertex_ai/claude-3-opus","vertex_ai/claude-3-opus@20240229","vertex_ai/claude-3-sonnet","vertex_ai/claude-3-sonnet@20240229","vertex_ai/claude-opus-4","vertex_ai/claude-opus-4-1","vertex_ai/claude-opus-4-1@20250805","vertex_ai/claude-opus-4-5","vertex_ai/claude-opus-4-5@20251101","vertex_ai/claude-sonnet-4-5","vertex_ai/claude-sonnet-4-5@20250929","vertex_ai/claude-opus-4@20250514","vertex_ai/claude-sonnet-4","vertex_ai/claude-sonnet-4@20250514","vertex_ai/mistralai/codestral-2@001","vertex_ai/codestral-2","vertex_ai/codestral-2@001","vertex_ai/mistralai/codestral-2","vertex_ai/codestral-2501","vertex_ai/codestral@2405","vertex_ai/codestral@latest","vertex_ai/deepseek-ai/deepseek-v3.1-maas","vertex_ai/deepseek-ai/deepseek-v3.2-maas","vertex_ai/deepseek-ai/deepseek-r1-0528-maas","vertex_ai/gemini-2.5-flash-image","vertex_ai/gemini-3-pro-image-preview","vertex_ai/imagegeneration@006","vertex_ai/imagen-3.0-fast-generate-001","vertex_ai/imagen-3.0-generate-001","vertex_ai/imagen-3.0-generate-002","vertex_ai/imagen-3.0-capability-001","vertex_ai/imagen-4.0-fast-generate-001","vertex_ai/imagen-4.0-generate-001","vertex_ai/imagen-4.0-ultra-generate-001","vertex_ai/jamba-1.5","vertex_ai/jamba-1.5-large","vertex_ai/jamba-1.5-large@001","vertex_ai/jamba-1.5-mini","vertex_ai/jamba-1.5-mini@001","vertex_ai/meta/llama-3.1-405b-instruct-maas","vertex_ai/meta/llama-3.1-70b-instruct-maas","vertex_ai/meta/llama-3.1-8b-instruct-maas","vertex_ai/meta/llama-3.2-90b-vision-instruct-maas","vertex_ai/meta/llama-4-maverick-17b-128e-instruct-maas","vertex_ai/meta/llama-4-maverick-17b-16e-instruct-maas","vertex_ai/meta/llama-4-scout-17b-128e-instruct-maas","vertex_ai/meta/llama-4-scout-17b-16e-instruct-maas","vertex_ai/meta/llama3-405b-instruct-maas","vertex_ai/meta/llama3-70b-instruct-maas","vertex_ai/meta/llama3-8b-instruct-maas","vertex_ai/minimaxai/minimax-m2-maas","vertex_ai/moonshotai/kimi-k2-thinking-maas","vertex_ai/mistral-medium-3","vertex_ai/mistral-medium-3@001","vertex_ai/mistralai/mistral-medium-3","vertex_ai/mistralai/mistral-medium-3@001","vertex_ai/mistral-large-2411","vertex_ai/mistral-large@2407","vertex_ai/mistral-large@2411-001","vertex_ai/mistral-large@latest","vertex_ai/mistral-nemo@2407","vertex_ai/mistral-nemo@latest","vertex_ai/mistral-small-2503","vertex_ai/mistral-small-2503@001","vertex_ai/mistral-ocr-2505","vertex_ai/openai/gpt-oss-120b-maas","vertex_ai/openai/gpt-oss-20b-maas","vertex_ai/qwen/qwen3-235b-a22b-instruct-2507-maas","vertex_ai/qwen/qwen3-coder-480b-a35b-instruct-maas","vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas","vertex_ai/qwen/qwen3-next-80b-a3b-thinking-maas","vertex_ai/veo-2.0-generate-001","vertex_ai/veo-3.0-fast-generate-preview","vertex_ai/veo-3.0-generate-preview","vertex_ai/veo-3.0-fast-generate-001","vertex_ai/veo-3.0-generate-001","vertex_ai/veo-3.1-generate-preview","vertex_ai/veo-3.1-fast-generate-preview","voyage/rerank-2","voyage/rerank-2-lite","voyage/rerank-2.5","voyage/rerank-2.5-lite","voyage/voyage-2","voyage/voyage-3","voyage/voyage-3-large","voyage/voyage-3-lite","voyage/voyage-3.5","voyage/voyage-3.5-lite","voyage/voyage-code-2","voyage/voyage-code-3","voyage/voyage-context-3","voyage/voyage-finance-2","voyage/voyage-large-2","voyage/voyage-law-2","voyage/voyage-lite-01","voyage/voyage-lite-02-instruct","voyage/voyage-multimodal-3","wandb/openai/gpt-oss-120b","wandb/openai/gpt-oss-20b","wandb/zai-org/GLM-4.5","wandb/Qwen/Qwen3-235B-A22B-Instruct-2507","wandb/Qwen/Qwen3-Coder-480B-A35B-Instruct","wandb/Qwen/Qwen3-235B-A22B-Thinking-2507","wandb/moonshotai/Kimi-K2-Instruct","wandb/meta-llama/Llama-3.1-8B-Instruct","wandb/deepseek-ai/DeepSeek-V3.1","wandb/deepseek-ai/DeepSeek-R1-0528","wandb/deepseek-ai/DeepSeek-V3-0324","wandb/meta-llama/Llama-3.3-70B-Instruct","wandb/meta-llama/Llama-4-Scout-17B-16E-Instruct","wandb/microsoft/Phi-4-mini-instruct","watsonx/ibm/granite-3-8b-instruct","watsonx/mistralai/mistral-large","watsonx/bigscience/mt0-xxl-13b","watsonx/core42/jais-13b-chat","watsonx/google/flan-t5-xl-3b","watsonx/ibm/granite-13b-chat-v2","watsonx/ibm/granite-13b-instruct-v2","watsonx/ibm/granite-3-3-8b-instruct","watsonx/ibm/granite-4-h-small","watsonx/ibm/granite-guardian-3-2-2b","watsonx/ibm/granite-guardian-3-3-8b","watsonx/ibm/granite-ttm-1024-96-r2","watsonx/ibm/granite-ttm-1536-96-r2","watsonx/ibm/granite-ttm-512-96-r2","watsonx/ibm/granite-vision-3-2-2b","watsonx/meta-llama/llama-3-2-11b-vision-instruct","watsonx/meta-llama/llama-3-2-1b-instruct","watsonx/meta-llama/llama-3-2-3b-instruct","watsonx/meta-llama/llama-3-2-90b-vision-instruct","watsonx/meta-llama/llama-3-3-70b-instruct","watsonx/meta-llama/llama-4-maverick-17b","watsonx/meta-llama/llama-guard-3-11b-vision","watsonx/mistralai/mistral-medium-2505","watsonx/mistralai/mistral-small-2503","watsonx/mistralai/mistral-small-3-1-24b-instruct-2503","watsonx/mistralai/pixtral-12b-2409","watsonx/openai/gpt-oss-120b","watsonx/sdaia/allam-1-13b-instruct","watsonx/whisper-large-v3-turbo","whisper-1","xai/grok-2","xai/grok-2-1212","xai/grok-2-latest","xai/grok-2-vision","xai/grok-2-vision-1212","xai/grok-2-vision-latest","xai/grok-3","xai/grok-3-beta","xai/grok-3-fast-beta","xai/grok-3-fast-latest","xai/grok-3-latest","xai/grok-3-mini","xai/grok-3-mini-beta","xai/grok-3-mini-fast","xai/grok-3-mini-fast-beta","xai/grok-3-mini-fast-latest","xai/grok-3-mini-latest","xai/grok-4","xai/grok-4-fast-reasoning","xai/grok-4-fast-non-reasoning","xai/grok-4-0709","xai/grok-4-latest","xai/grok-4-1-fast","xai/grok-4-1-fast-reasoning","xai/grok-4-1-fast-reasoning-latest","xai/grok-4-1-fast-non-reasoning","xai/grok-4-1-fast-non-reasoning-latest","xai/grok-beta","xai/grok-code-fast","xai/grok-code-fast-1","xai/grok-code-fast-1-0825","xai/grok-vision-beta","zai/glm-4.6","zai/glm-4.5","zai/glm-4.5v","zai/glm-4.5-x","zai/glm-4.5-air","zai/glm-4.5-airx","zai/glm-4-32b-0414-128k","zai/glm-4.5-flash","vertex_ai/search_api","openai/container","openai/sora-2","openai/sora-2-pro","azure/sora-2","azure/sora-2-pro","azure/sora-2-pro-high-res","runwayml/gen4_turbo","runwayml/gen4_aleph","runwayml/gen3a_turbo","runwayml/gen4_image","runwayml/gen4_image_turbo","runwayml/eleven_multilingual_v2","fireworks_ai/accounts/fireworks/models/qwen3-coder-480b-a35b-instruct","fireworks_ai/accounts/fireworks/models/flux-kontext-pro","fireworks_ai/accounts/fireworks/models/SSD-1B","fireworks_ai/accounts/fireworks/models/chronos-hermes-13b-v2","fireworks_ai/accounts/fireworks/models/code-llama-13b","fireworks_ai/accounts/fireworks/models/code-llama-13b-instruct","fireworks_ai/accounts/fireworks/models/code-llama-13b-python","fireworks_ai/accounts/fireworks/models/code-llama-34b","fireworks_ai/accounts/fireworks/models/code-llama-34b-instruct","fireworks_ai/accounts/fireworks/models/code-llama-34b-python","fireworks_ai/accounts/fireworks/models/code-llama-70b","fireworks_ai/accounts/fireworks/models/code-llama-70b-instruct","fireworks_ai/accounts/fireworks/models/code-llama-70b-python","fireworks_ai/accounts/fireworks/models/code-llama-7b","fireworks_ai/accounts/fireworks/models/code-llama-7b-instruct","fireworks_ai/accounts/fireworks/models/code-llama-7b-python","fireworks_ai/accounts/fireworks/models/code-qwen-1p5-7b","fireworks_ai/accounts/fireworks/models/codegemma-2b","fireworks_ai/accounts/fireworks/models/codegemma-7b","fireworks_ai/accounts/fireworks/models/cogito-671b-v2-p1","fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-3b","fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-70b","fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-8b","fireworks_ai/accounts/fireworks/models/cogito-v1-preview-qwen-14b","fireworks_ai/accounts/fireworks/models/cogito-v1-preview-qwen-32b","fireworks_ai/accounts/fireworks/models/flux-kontext-max","fireworks_ai/accounts/fireworks/models/dbrx-instruct","fireworks_ai/accounts/fireworks/models/deepseek-coder-1b-base","fireworks_ai/accounts/fireworks/models/deepseek-coder-33b-instruct","fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-base","fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-base-v1p5","fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-instruct-v1p5","fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-lite-base","fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-lite-instruct","fireworks_ai/accounts/fireworks/models/deepseek-prover-v2","fireworks_ai/accounts/fireworks/models/deepseek-r1-0528-distill-qwen3-8b","fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-llama-70b","fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-llama-8b","fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-14b","fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-1p5b","fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-32b","fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-7b","fireworks_ai/accounts/fireworks/models/deepseek-v2-lite-chat","fireworks_ai/accounts/fireworks/models/deepseek-v2p5","fireworks_ai/accounts/fireworks/models/devstral-small-2505","fireworks_ai/accounts/fireworks/models/dobby-mini-unhinged-plus-llama-3-1-8b","fireworks_ai/accounts/fireworks/models/dobby-unhinged-llama-3-3-70b-new","fireworks_ai/accounts/fireworks/models/dolphin-2-9-2-qwen2-72b","fireworks_ai/accounts/fireworks/models/dolphin-2p6-mixtral-8x7b","fireworks_ai/accounts/fireworks/models/ernie-4p5-21b-a3b-pt","fireworks_ai/accounts/fireworks/models/ernie-4p5-300b-a47b-pt","fireworks_ai/accounts/fireworks/models/fare-20b","fireworks_ai/accounts/fireworks/models/firefunction-v1","fireworks_ai/accounts/fireworks/models/firellava-13b","fireworks_ai/accounts/fireworks/models/firesearch-ocr-v6","fireworks_ai/accounts/fireworks/models/fireworks-asr-large","fireworks_ai/accounts/fireworks/models/fireworks-asr-v2","fireworks_ai/accounts/fireworks/models/flux-1-dev","fireworks_ai/accounts/fireworks/models/flux-1-dev-controlnet-union","fireworks_ai/accounts/fireworks/models/flux-1-dev-fp8","fireworks_ai/accounts/fireworks/models/flux-1-schnell","fireworks_ai/accounts/fireworks/models/flux-1-schnell-fp8","fireworks_ai/accounts/fireworks/models/gemma-2b-it","fireworks_ai/accounts/fireworks/models/gemma-3-27b-it","fireworks_ai/accounts/fireworks/models/gemma-7b","fireworks_ai/accounts/fireworks/models/gemma-7b-it","fireworks_ai/accounts/fireworks/models/gemma2-9b-it","fireworks_ai/accounts/fireworks/models/glm-4p5v","fireworks_ai/accounts/fireworks/models/gpt-oss-safeguard-120b","fireworks_ai/accounts/fireworks/models/gpt-oss-safeguard-20b","fireworks_ai/accounts/fireworks/models/hermes-2-pro-mistral-7b","fireworks_ai/accounts/fireworks/models/internvl3-38b","fireworks_ai/accounts/fireworks/models/internvl3-78b","fireworks_ai/accounts/fireworks/models/internvl3-8b","fireworks_ai/accounts/fireworks/models/japanese-stable-diffusion-xl","fireworks_ai/accounts/fireworks/models/kat-coder","fireworks_ai/accounts/fireworks/models/kat-dev-32b","fireworks_ai/accounts/fireworks/models/kat-dev-72b-exp","fireworks_ai/accounts/fireworks/models/llama-guard-2-8b","fireworks_ai/accounts/fireworks/models/llama-guard-3-1b","fireworks_ai/accounts/fireworks/models/llama-guard-3-8b","fireworks_ai/accounts/fireworks/models/llama-v2-13b","fireworks_ai/accounts/fireworks/models/llama-v2-13b-chat","fireworks_ai/accounts/fireworks/models/llama-v2-70b","fireworks_ai/accounts/fireworks/models/llama-v2-70b-chat","fireworks_ai/accounts/fireworks/models/llama-v2-7b","fireworks_ai/accounts/fireworks/models/llama-v2-7b-chat","fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct","fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct-hf","fireworks_ai/accounts/fireworks/models/llama-v3-8b","fireworks_ai/accounts/fireworks/models/llama-v3-8b-instruct-hf","fireworks_ai/accounts/fireworks/models/llama-v3p1-405b-instruct-long","fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct","fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct-1b","fireworks_ai/accounts/fireworks/models/llama-v3p1-nemotron-70b-instruct","fireworks_ai/accounts/fireworks/models/llama-v3p2-1b","fireworks_ai/accounts/fireworks/models/llama-v3p2-3b","fireworks_ai/accounts/fireworks/models/llama-v3p3-70b-instruct","fireworks_ai/accounts/fireworks/models/llamaguard-7b","fireworks_ai/accounts/fireworks/models/llava-yi-34b","fireworks_ai/accounts/fireworks/models/minimax-m1-80k","fireworks_ai/accounts/fireworks/models/minimax-m2","fireworks_ai/accounts/fireworks/models/ministral-3-14b-instruct-2512","fireworks_ai/accounts/fireworks/models/ministral-3-3b-instruct-2512","fireworks_ai/accounts/fireworks/models/ministral-3-8b-instruct-2512","fireworks_ai/accounts/fireworks/models/mistral-7b","fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-4k","fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-v0p2","fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-v3","fireworks_ai/accounts/fireworks/models/mistral-7b-v0p2","fireworks_ai/accounts/fireworks/models/mistral-large-3-fp8","fireworks_ai/accounts/fireworks/models/mistral-nemo-base-2407","fireworks_ai/accounts/fireworks/models/mistral-nemo-instruct-2407","fireworks_ai/accounts/fireworks/models/mistral-small-24b-instruct-2501","fireworks_ai/accounts/fireworks/models/mixtral-8x22b","fireworks_ai/accounts/fireworks/models/mixtral-8x22b-instruct","fireworks_ai/accounts/fireworks/models/mixtral-8x7b","fireworks_ai/accounts/fireworks/models/mixtral-8x7b-instruct","fireworks_ai/accounts/fireworks/models/mixtral-8x7b-instruct-hf","fireworks_ai/accounts/fireworks/models/mythomax-l2-13b","fireworks_ai/accounts/fireworks/models/nemotron-nano-v2-12b-vl","fireworks_ai/accounts/fireworks/models/nous-capybara-7b-v1p9","fireworks_ai/accounts/fireworks/models/nous-hermes-2-mixtral-8x7b-dpo","fireworks_ai/accounts/fireworks/models/nous-hermes-2-yi-34b","fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-13b","fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-70b","fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-7b","fireworks_ai/accounts/fireworks/models/nvidia-nemotron-nano-12b-v2","fireworks_ai/accounts/fireworks/models/nvidia-nemotron-nano-9b-v2","fireworks_ai/accounts/fireworks/models/openchat-3p5-0106-7b","fireworks_ai/accounts/fireworks/models/openhermes-2-mistral-7b","fireworks_ai/accounts/fireworks/models/openhermes-2p5-mistral-7b","fireworks_ai/accounts/fireworks/models/openorca-7b","fireworks_ai/accounts/fireworks/models/phi-2-3b","fireworks_ai/accounts/fireworks/models/phi-3-mini-128k-instruct","fireworks_ai/accounts/fireworks/models/phi-3-vision-128k-instruct","fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-python-v1","fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-v1","fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-v2","fireworks_ai/accounts/fireworks/models/playground-v2-1024px-aesthetic","fireworks_ai/accounts/fireworks/models/playground-v2-5-1024px-aesthetic","fireworks_ai/accounts/fireworks/models/pythia-12b","fireworks_ai/accounts/fireworks/models/qwen-qwq-32b-preview","fireworks_ai/accounts/fireworks/models/qwen-v2p5-14b-instruct","fireworks_ai/accounts/fireworks/models/qwen-v2p5-7b","fireworks_ai/accounts/fireworks/models/qwen1p5-72b-chat","fireworks_ai/accounts/fireworks/models/qwen2-7b-instruct","fireworks_ai/accounts/fireworks/models/qwen2-vl-2b-instruct","fireworks_ai/accounts/fireworks/models/qwen2-vl-72b-instruct","fireworks_ai/accounts/fireworks/models/qwen2-vl-7b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-0p5b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-14b","fireworks_ai/accounts/fireworks/models/qwen2p5-1p5b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-32b","fireworks_ai/accounts/fireworks/models/qwen2p5-32b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-72b","fireworks_ai/accounts/fireworks/models/qwen2p5-72b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-7b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-0p5b","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-0p5b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-14b","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-14b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-1p5b","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-1p5b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-128k","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-32k-rope","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-64k","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-3b","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-3b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-7b","fireworks_ai/accounts/fireworks/models/qwen2p5-coder-7b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-math-72b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-vl-32b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-vl-3b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-vl-72b-instruct","fireworks_ai/accounts/fireworks/models/qwen2p5-vl-7b-instruct","fireworks_ai/accounts/fireworks/models/qwen3-0p6b","fireworks_ai/accounts/fireworks/models/qwen3-14b","fireworks_ai/accounts/fireworks/models/qwen3-1p7b","fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft","fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft-131072","fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft-40960","fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b","fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b-instruct-2507","fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b-thinking-2507","fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b","fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b-instruct-2507","fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b-thinking-2507","fireworks_ai/accounts/fireworks/models/qwen3-32b","fireworks_ai/accounts/fireworks/models/qwen3-4b","fireworks_ai/accounts/fireworks/models/qwen3-4b-instruct-2507","fireworks_ai/accounts/fireworks/models/qwen3-8b","fireworks_ai/accounts/fireworks/models/qwen3-coder-30b-a3b-instruct","fireworks_ai/accounts/fireworks/models/qwen3-coder-480b-instruct-bf16","fireworks_ai/accounts/fireworks/models/qwen3-embedding-0p6b","fireworks_ai/accounts/fireworks/models/qwen3-embedding-4b","fireworks_ai/accounts/fireworks/models/qwen3-embedding-8b","fireworks_ai/accounts/fireworks/models/qwen3-next-80b-a3b-instruct","fireworks_ai/accounts/fireworks/models/qwen3-next-80b-a3b-thinking","fireworks_ai/accounts/fireworks/models/qwen3-reranker-0p6b","fireworks_ai/accounts/fireworks/models/qwen3-reranker-4b","fireworks_ai/accounts/fireworks/models/qwen3-reranker-8b","fireworks_ai/accounts/fireworks/models/qwen3-vl-235b-a22b-instruct","fireworks_ai/accounts/fireworks/models/qwen3-vl-235b-a22b-thinking","fireworks_ai/accounts/fireworks/models/qwen3-vl-30b-a3b-instruct","fireworks_ai/accounts/fireworks/models/qwen3-vl-30b-a3b-thinking","fireworks_ai/accounts/fireworks/models/qwen3-vl-32b-instruct","fireworks_ai/accounts/fireworks/models/qwen3-vl-8b-instruct","fireworks_ai/accounts/fireworks/models/qwq-32b","fireworks_ai/accounts/fireworks/models/rolm-ocr","fireworks_ai/accounts/fireworks/models/snorkel-mistral-7b-pairrm-dpo","fireworks_ai/accounts/fireworks/models/stable-diffusion-xl-1024-v1-0","fireworks_ai/accounts/fireworks/models/stablecode-3b","fireworks_ai/accounts/fireworks/models/starcoder-16b","fireworks_ai/accounts/fireworks/models/starcoder-7b","fireworks_ai/accounts/fireworks/models/starcoder2-15b","fireworks_ai/accounts/fireworks/models/starcoder2-3b","fireworks_ai/accounts/fireworks/models/starcoder2-7b","fireworks_ai/accounts/fireworks/models/toppy-m-7b","fireworks_ai/accounts/fireworks/models/whisper-v3","fireworks_ai/accounts/fireworks/models/whisper-v3-turbo","fireworks_ai/accounts/fireworks/models/yi-34b","fireworks_ai/accounts/fireworks/models/yi-34b-200k-capybara","fireworks_ai/accounts/fireworks/models/yi-34b-chat","fireworks_ai/accounts/fireworks/models/yi-6b","fireworks_ai/accounts/fireworks/models/zephyr-7b-beta"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"gpt-4o-mini"},"user_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"User Message","dynamic":false,"info":"User message to pass to the run.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"user_message","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"AstraDB":{"base_classes":["Data","DataFrame","VectorStore"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Ingest and search documents in Astra DB","display_name":"Astra DB","documentation":"https://docs.langflow.org/bundles-datastax","edited":false,"field_order":["token","environment","database_name","api_endpoint","keyspace","collection_name","autodetect_collection","ingest_data","search_query","should_cache_vector_store","embedding_model","content_field","deletion_field","ignore_invalid_documents","astradb_vectorstore_kwargs","search_method","reranker","lexical_terms","number_of_results","search_type","search_score_threshold","advanced_search_filter"],"frozen":false,"icon":"AstraDB","legacy":false,"metadata":{"code_hash":"d52094e54e96","dependencies":{"dependencies":[{"name":"astrapy","version":"2.1.0"},{"name":"langchain_core","version":"0.3.80"},{"name":"lfx","version":null},{"name":"langchain_astradb","version":"0.6.1"}],"total_dependencies":4},"module":"lfx.components.datastax.astradb_vectorstore.AstraDBVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Vector Store Connection","group_outputs":false,"hidden":false,"method":"as_vector_store","name":"vectorstoreconnection","selected":"VectorStore","tool_mode":true,"types":["VectorStore"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","advanced_search_filter":{"_input_type":"NestedDictInput","advanced":true,"display_name":"Search Metadata Filter","dynamic":false,"info":"Optional dictionary of filters to apply to the search query.","list":false,"list_add_label":"Add More","name":"advanced_search_filter","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":"NestedDict","value":{}},"api_endpoint":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Astra DB API Endpoint","dynamic":false,"external_options":{},"info":"The API Endpoint for the Astra DB instance. Supercedes database selection.","name":"api_endpoint","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"astradb_vectorstore_kwargs":{"_input_type":"NestedDictInput","advanced":true,"display_name":"AstraDBVectorStore Parameters","dynamic":false,"info":"Optional dictionary of additional parameters for the AstraDBVectorStore.","list":false,"list_add_label":"Add More","name":"astradb_vectorstore_kwargs","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":"NestedDict","value":{}},"autodetect_collection":{"_input_type":"BoolInput","advanced":true,"display_name":"Autodetect Collection","dynamic":false,"info":"Boolean flag to determine whether to autodetect the collection.","list":false,"list_add_label":"Add More","name":"autodetect_collection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from astrapy import DataAPIClient\nfrom langchain_core.documents import Document\n\nfrom lfx.base.datastax.astradb_base import AstraDBBaseComponent\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.base.vectorstores.vector_store_connection_decorator import vector_store_connection\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.io import BoolInput, DropdownInput, FloatInput, HandleInput, IntInput, NestedDictInput, QueryInput, StrInput\nfrom lfx.schema.data import Data\nfrom lfx.serialization import serialize\nfrom lfx.utils.version import get_version_info\n\n\n@vector_store_connection\nclass AstraDBVectorStoreComponent(AstraDBBaseComponent, LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.langflow.org/bundles-datastax\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n inputs = [\n *AstraDBBaseComponent.inputs,\n *LCVectorStoreComponent.inputs,\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Specify the Embedding Model. Not required for Astra Vectorize collections.\",\n required=False,\n show=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n StrInput(\n name=\"deletion_field\",\n display_name=\"Deletion Based On Field\",\n info=\"When this parameter is provided, documents in the target collection with \"\n \"metadata field values matching the input metadata field value will be deleted \"\n \"before new data is loaded.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"search_method\",\n display_name=\"Search Method\",\n info=(\n \"Determine how your content is matched: Vector finds semantic similarity, \"\n \"and Hybrid Search (suggested) combines both approaches \"\n \"with a reranker.\"\n ),\n options=[\"Hybrid Search\", \"Vector Search\"], # TODO: Restore Lexical Search?\n options_metadata=[{\"icon\": \"SearchHybrid\"}, {\"icon\": \"SearchVector\"}],\n value=\"Vector Search\",\n advanced=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"reranker\",\n display_name=\"Reranker\",\n info=\"Post-retrieval model that re-scores results for optimal relevance ranking.\",\n show=False,\n toggle=True,\n ),\n QueryInput(\n name=\"lexical_terms\",\n display_name=\"Lexical Terms\",\n info=\"Add additional terms/keywords to augment search precision.\",\n placeholder=\"Enter terms to search...\",\n separator=\" \",\n show=False,\n value=\"\",\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n ]\n\n async def update_build_config(\n self,\n build_config: dict,\n field_value: str | dict,\n field_name: str | None = None,\n ) -> dict:\n \"\"\"Update build configuration with proper handling of embedding and search options.\"\"\"\n # Handle base astra db build config updates\n build_config = await super().update_build_config(\n build_config,\n field_value=field_value,\n field_name=field_name,\n )\n\n # Set embedding model display based on provider selection\n if isinstance(field_value, dict) and \"02_embedding_generation_provider\" in field_value:\n embedding_provider = field_value.get(\"02_embedding_generation_provider\")\n is_custom_provider = embedding_provider and embedding_provider != \"Bring your own\"\n provider = embedding_provider.lower() if is_custom_provider and embedding_provider is not None else None\n\n build_config[\"embedding_model\"][\"show\"] = not bool(provider)\n build_config[\"embedding_model\"][\"required\"] = not bool(provider)\n\n # Early return if no API endpoint is configured\n if not self.get_api_endpoint():\n return build_config\n\n # Configure search method and related options\n return self._configure_search_options(build_config)\n\n def _configure_search_options(self, build_config: dict) -> dict:\n \"\"\"Configure hybrid search, reranker, and vector search options.\"\"\"\n # Detect available hybrid search capabilities\n hybrid_capabilities = self._detect_hybrid_capabilities()\n\n # Return if we haven't selected a collection\n if not build_config[\"collection_name\"][\"options\"] or not build_config[\"collection_name\"][\"value\"]:\n return build_config\n\n # Get collection options\n collection_options = self._get_collection_options(build_config)\n\n # Get the selected collection index\n index = build_config[\"collection_name\"][\"options\"].index(build_config[\"collection_name\"][\"value\"])\n provider = build_config[\"collection_name\"][\"options_metadata\"][index][\"provider\"]\n build_config[\"embedding_model\"][\"show\"] = not bool(provider)\n build_config[\"embedding_model\"][\"required\"] = not bool(provider)\n\n # Determine search configuration\n is_vector_search = build_config[\"search_method\"][\"value\"] == \"Vector Search\"\n is_autodetect = build_config[\"autodetect_collection\"][\"value\"]\n\n # Apply hybrid search configuration\n if hybrid_capabilities[\"available\"]:\n build_config[\"search_method\"][\"show\"] = True\n build_config[\"search_method\"][\"options\"] = [\"Hybrid Search\", \"Vector Search\"]\n build_config[\"search_method\"][\"value\"] = build_config[\"search_method\"].get(\"value\", \"Hybrid Search\")\n\n build_config[\"reranker\"][\"options\"] = hybrid_capabilities[\"reranker_models\"]\n build_config[\"reranker\"][\"options_metadata\"] = hybrid_capabilities[\"reranker_metadata\"]\n if hybrid_capabilities[\"reranker_models\"]:\n build_config[\"reranker\"][\"value\"] = hybrid_capabilities[\"reranker_models\"][0]\n else:\n build_config[\"search_method\"][\"show\"] = False\n build_config[\"search_method\"][\"options\"] = [\"Vector Search\"]\n build_config[\"search_method\"][\"value\"] = \"Vector Search\"\n build_config[\"reranker\"][\"options\"] = []\n build_config[\"reranker\"][\"options_metadata\"] = []\n\n # Configure reranker visibility and state\n hybrid_enabled = (\n collection_options[\"rerank_enabled\"] and build_config[\"search_method\"][\"value\"] == \"Hybrid Search\"\n )\n\n build_config[\"reranker\"][\"show\"] = hybrid_enabled\n build_config[\"reranker\"][\"toggle_value\"] = hybrid_enabled\n build_config[\"reranker\"][\"toggle_disable\"] = is_vector_search\n\n # Configure lexical terms\n lexical_visible = collection_options[\"lexical_enabled\"] and not is_vector_search\n build_config[\"lexical_terms\"][\"show\"] = lexical_visible\n build_config[\"lexical_terms\"][\"value\"] = \"\" if is_vector_search else build_config[\"lexical_terms\"][\"value\"]\n\n # Configure search type and score threshold\n build_config[\"search_type\"][\"show\"] = is_vector_search\n build_config[\"search_score_threshold\"][\"show\"] = is_vector_search\n\n # Force similarity search for hybrid mode or autodetect\n if hybrid_enabled or is_autodetect:\n build_config[\"search_type\"][\"value\"] = \"Similarity\"\n\n return build_config\n\n def _detect_hybrid_capabilities(self) -> dict:\n \"\"\"Detect available hybrid search and reranking capabilities.\"\"\"\n environment = self.get_environment(self.environment)\n client = DataAPIClient(environment=environment)\n admin_client = client.get_admin()\n db_admin = admin_client.get_database_admin(self.get_api_endpoint(), token=self.token)\n\n try:\n providers = db_admin.find_reranking_providers()\n reranker_models = [\n model.name for provider_data in providers.reranking_providers.values() for model in provider_data.models\n ]\n reranker_metadata = [\n {\"icon\": self.get_provider_icon(provider_name=model.name.split(\"/\")[0])}\n for provider in providers.reranking_providers.values()\n for model in provider.models\n ]\n except Exception as e: # noqa: BLE001\n self.log(f\"Hybrid search not available: {e}\")\n return {\n \"available\": False,\n \"reranker_models\": [],\n \"reranker_metadata\": [],\n }\n else:\n return {\n \"available\": True,\n \"reranker_models\": reranker_models,\n \"reranker_metadata\": reranker_metadata,\n }\n\n def _get_collection_options(self, build_config: dict) -> dict:\n \"\"\"Retrieve collection-level search options.\"\"\"\n database = self.get_database_object(api_endpoint=build_config[\"api_endpoint\"][\"value\"])\n collection = database.get_collection(\n name=build_config[\"collection_name\"][\"value\"],\n keyspace=build_config[\"keyspace\"][\"value\"],\n )\n\n col_options = collection.options()\n\n return {\n \"rerank_enabled\": bool(col_options.rerank and col_options.rerank.enabled),\n \"lexical_enabled\": bool(col_options.lexical and col_options.lexical.enabled),\n }\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBVectorStore\n from langchain_astradb.utils.astradb import HybridSearchMode\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Get the embedding model and additional params\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_model else {}\n\n # Get the additional parameters\n additional_params = self.astradb_vectorstore_kwargs or {}\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n # if os.getenv(\"AWS_EXECUTION_ENV\") == \"AWS_ECS_FARGATE\": # TODO: More precise way of detecting\n # langflow_prefix = \"ds-\"\n\n # Get the database object\n database = self.get_database_object()\n autodetect = self.collection_name in database.list_collection_names() and self.autodetect_collection\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n \"autodetect_collection\": autodetect,\n \"content_field\": (\n self.content_field\n if self.content_field and embedding_params\n else (\n \"page_content\"\n if embedding_params\n and self.collection_data(collection_name=self.collection_name, database=database) == 0\n else None\n )\n ),\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Choose HybridSearchMode based on the selected param\n hybrid_search_mode = HybridSearchMode.DEFAULT if self.search_method == \"Hybrid Search\" else HybridSearchMode.OFF\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=database.api_endpoint,\n namespace=database.keyspace,\n collection_name=self.collection_name,\n environment=self.environment,\n # Hybrid Search Parameters\n hybrid_search=hybrid_search_mode,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params,\n **embedding_params,\n **additional_params,\n )\n except ValueError as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n # Add documents to the vector store\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n documents = [\n Document(page_content=doc.page_content, metadata=serialize(doc.metadata, to_str=True)) for doc in documents\n ]\n\n if documents and self.deletion_field:\n self.log(f\"Deleting documents where {self.deletion_field}\")\n try:\n database = self.get_database_object()\n collection = database.get_collection(self.collection_name, keyspace=database.keyspace)\n delete_values = list({doc.metadata[self.deletion_field] for doc in documents})\n self.log(f\"Deleting documents where {self.deletion_field} matches {delete_values}.\")\n collection.delete_many({f\"metadata.{self.deletion_field}\": {\"$in\": delete_values}})\n except ValueError as e:\n msg = f\"Error deleting documents from AstraDBVectorStore based on '{self.deletion_field}': {e}\"\n raise ValueError(msg) from e\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except ValueError as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n search_type_mapping = {\n \"Similarity with score threshold\": \"similarity_score_threshold\",\n \"MMR (Max Marginal Relevance)\": \"mmr\",\n }\n\n return search_type_mapping.get(self.search_type, \"similarity\")\n\n def _build_search_args(self):\n # Clean up the search query\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n lexical_terms = self.lexical_terms or None\n\n # Check if we have a search query, and if so set the args\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n \"lexical_query\": lexical_terms,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n self.log(f\"store.hybrid_search: {vector_store.hybrid_search}\")\n self.log(f\"Lexical terms: {self.lexical_terms}\")\n self.log(f\"Reranker: {self.reranker}\")\n\n try:\n search_args = self._build_search_args()\n except ValueError as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except ValueError as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"},"collection_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several seconds for creation to complete.","display_name":"Create new collection","field_order":["01_new_collection_name","02_embedding_generation_provider","03_embedding_generation_model","04_dimension"],"name":"create_collection","template":{"01_new_collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new collection to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_collection_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_embedding_generation_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding generation method","dynamic":false,"external_options":{},"helper_text":"To create collections with more embedding provider options, go to your database in Astra DB","info":"Provider to use for generating embeddings.","name":"embedding_generation_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_embedding_generation_model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding model","dynamic":false,"external_options":{},"info":"Model to use for generating embeddings.","name":"embedding_generation_model","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"04_dimension":{"_input_type":"IntInput","advanced":false,"display_name":"Dimensions","dynamic":false,"info":"Dimensions of the embeddings to generate.","list":false,"list_add_label":"Add More","name":"dimension","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int"}}}}},"functionality":"create"},"display_name":"Collection","dynamic":false,"external_options":{},"info":"The name of the collection within Astra DB where the vectors will be stored.","name":"collection_name","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"refresh_button":true,"required":true,"show":false,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"content_field":{"_input_type":"StrInput","advanced":true,"display_name":"Content Field","dynamic":false,"info":"Field to use as the text content field for the vector store.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"content_field","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"database_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several minutes for creation to complete.","display_name":"Create new database","field_order":["01_new_database_name","02_cloud_provider","03_region"],"name":"create_database","template":{"01_new_database_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new database to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_database_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_cloud_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Cloud provider","dynamic":false,"external_options":{},"info":"Cloud provider for the new database.","name":"cloud_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_region":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region","dynamic":false,"external_options":{},"info":"Region for the new database.","name":"region","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""}}}}},"functionality":"create"},"display_name":"Database","dynamic":false,"external_options":{},"info":"The Database name for the Astra DB instance.","name":"database_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":""},"deletion_field":{"_input_type":"StrInput","advanced":true,"display_name":"Deletion Based On Field","dynamic":false,"info":"When this parameter is provided, documents in the target collection with metadata field values matching the input metadata field value will be deleted before new data is loaded.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"deletion_field","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"embedding_model":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding Model","dynamic":false,"info":"Specify the Embedding Model. Not required for Astra Vectorize collections.","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding_model","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"environment":{"_input_type":"DropdownInput","advanced":true,"combobox":true,"dialog_inputs":{},"display_name":"Environment","dynamic":false,"external_options":{},"info":"The environment for the Astra DB API Endpoint.","name":"environment","options":["prod","test","dev"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"prod"},"ignore_invalid_documents":{"_input_type":"BoolInput","advanced":true,"display_name":"Ignore Invalid Documents","dynamic":false,"info":"Boolean flag to determine whether to ignore invalid documents at runtime.","list":false,"list_add_label":"Add More","name":"ignore_invalid_documents","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"keyspace":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Keyspace","dynamic":false,"external_options":{},"info":"Optional keyspace within Astra DB to use for the collection.","name":"keyspace","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"lexical_terms":{"_input_type":"QueryInput","advanced":false,"display_name":"Lexical Terms","dynamic":false,"info":"Add additional terms/keywords to augment search precision.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"lexical_terms","override_skip":false,"placeholder":"Enter terms to search...","required":false,"separator":" ","show":false,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Search Results","dynamic":false,"info":"Number of search results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"reranker":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Reranker","dynamic":false,"external_options":{},"info":"Post-retrieval model that re-scores results for optimal relevance ranking.","name":"reranker","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"toggle":true,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"search_method":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Method","dynamic":false,"external_options":{},"info":"Determine how your content is matched: Vector finds semantic similarity, and Hybrid Search (suggested) combines both approaches with a reranker.","name":"search_method","options":["Hybrid Search","Vector Search"],"options_metadata":[{"icon":"SearchHybrid"},{"icon":"SearchVector"}],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Vector Search"},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"search_score_threshold":{"_input_type":"FloatInput","advanced":true,"display_name":"Search Score Threshold","dynamic":false,"info":"Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')","list":false,"list_add_label":"Add More","name":"search_score_threshold","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.0},"search_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Type","dynamic":false,"external_options":{},"info":"Search type to use","name":"search_type","options":["Similarity","Similarity with score threshold","MMR (Max Marginal Relevance)"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Similarity"},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Astra DB Application Token","dynamic":false,"info":"Authentication token for accessing Astra DB.","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"ASTRA_DB_APPLICATION_TOKEN"}},"tool_mode":false},"AstraDBCQLToolComponent":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Create a tool to get transactional data from DataStax Astra DB CQL Table","display_name":"Astra DB CQL","documentation":"https://docs.langflow.org/bundles-datastax","edited":false,"field_order":["token","environment","database_name","api_endpoint","keyspace","collection_name","autodetect_collection","tool_name","tool_description","projection_fields","tools_params","partition_keys","clustering_keys","static_filters","number_of_results"],"frozen":false,"icon":"AstraDB","legacy":false,"metadata":{"code_hash":"70c4523f841d","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"langchain_core","version":"0.3.80"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.datastax.astradb_cql.AstraDBCQLToolComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_endpoint":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Astra DB API Endpoint","dynamic":false,"external_options":{},"info":"The API Endpoint for the Astra DB instance. Supercedes database selection.","name":"api_endpoint","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"autodetect_collection":{"_input_type":"BoolInput","advanced":true,"display_name":"Autodetect Collection","dynamic":false,"info":"Boolean flag to determine whether to autodetect the collection.","list":false,"list_add_label":"Add More","name":"autodetect_collection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"clustering_keys":{"_input_type":"DictInput","advanced":true,"display_name":"DEPRECATED: Clustering Keys","dynamic":false,"info":"Field name and description to the model","list":true,"list_add_label":"Add More","name":"clustering_keys","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","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 urllib\nfrom datetime import datetime, timezone\nfrom http import HTTPStatus\nfrom typing import Any\n\nimport requests\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import BaseModel, Field, create_model\n\nfrom lfx.base.datastax.astradb_base import AstraDBBaseComponent\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.io import DictInput, IntInput, StrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.table import EditMode\n\n\nclass AstraDBCQLToolComponent(AstraDBBaseComponent, LCToolComponent):\n display_name: str = \"Astra DB CQL\"\n description: str = \"Create a tool to get transactional data from DataStax Astra DB CQL Table\"\n documentation: str = \"https://docs.langflow.org/bundles-datastax\"\n icon: str = \"AstraDB\"\n\n inputs = [\n *AstraDBBaseComponent.inputs,\n StrInput(name=\"tool_name\", display_name=\"Tool Name\", info=\"The name of the tool.\", required=True),\n StrInput(\n name=\"tool_description\",\n display_name=\"Tool Description\",\n info=\"The tool description to be passed to the model.\",\n required=True,\n ),\n StrInput(\n name=\"projection_fields\",\n display_name=\"Projection fields\",\n info=\"Attributes to return separated by comma.\",\n required=True,\n value=\"*\",\n advanced=True,\n ),\n TableInput(\n name=\"tools_params\",\n display_name=\"Tools Parameters\",\n info=\"Define the structure for the tool parameters. Describe the parameters \"\n \"in a way the LLM can understand how to use them. Add the parameters \"\n \"respecting the table schema (Partition Keys, Clustering Keys and Indexed Fields).\",\n required=False,\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Name of the field/parameter to be used by the model.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"field_name\",\n \"display_name\": \"Field Name\",\n \"type\": \"str\",\n \"description\": \"Specify the column name to be filtered on the table. \"\n \"Leave empty if the attribute name is the same as the name of the field.\",\n \"default\": \"\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the parameter.\",\n \"default\": \"description of tool parameter\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"mandatory\",\n \"display_name\": \"Is Mandatory\",\n \"type\": \"boolean\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate if the field is mandatory.\"),\n \"options\": [\"True\", \"False\"],\n \"default\": \"False\",\n },\n {\n \"name\": \"is_timestamp\",\n \"display_name\": \"Is Timestamp\",\n \"type\": \"boolean\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate if the field is a timestamp.\"),\n \"options\": [\"True\", \"False\"],\n \"default\": \"False\",\n },\n {\n \"name\": \"operator\",\n \"display_name\": \"Operator\",\n \"type\": \"str\",\n \"description\": \"Set the operator for the field. \"\n \"https://docs.datastax.com/en/astra-db-serverless/api-reference/documents.html#operators\",\n \"default\": \"$eq\",\n \"options\": [\"$gt\", \"$gte\", \"$lt\", \"$lte\", \"$eq\", \"$ne\", \"$in\", \"$nin\", \"$exists\", \"$all\", \"$size\"],\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[],\n ),\n DictInput(\n name=\"partition_keys\",\n display_name=\"DEPRECATED: Partition Keys\",\n is_list=True,\n info=\"Field name and description to the model\",\n required=False,\n advanced=True,\n ),\n DictInput(\n name=\"clustering_keys\",\n display_name=\"DEPRECATED: Clustering Keys\",\n is_list=True,\n info=\"Field name and description to the model\",\n required=False,\n advanced=True,\n ),\n DictInput(\n name=\"static_filters\",\n display_name=\"Static Filters\",\n is_list=True,\n advanced=True,\n info=\"Field name and value. When filled, it will not be generated by the LLM.\",\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=5,\n ),\n ]\n\n def parse_timestamp(self, timestamp_str: str) -> str:\n \"\"\"Parse a timestamp string into Astra DB REST API format.\n\n Args:\n timestamp_str (str): Input timestamp string\n\n Returns:\n str: Formatted timestamp string in YYYY-MM-DDTHH:MI:SS.000Z format\n\n Raises:\n ValueError: If the timestamp cannot be parsed\n \"\"\"\n # Common datetime formats to try\n formats = [\n \"%Y-%m-%d\", # 2024-03-21\n \"%Y-%m-%dT%H:%M:%S\", # 2024-03-21T15:30:00\n \"%Y-%m-%dT%H:%M:%S%z\", # 2024-03-21T15:30:00+0000\n \"%Y-%m-%d %H:%M:%S\", # 2024-03-21 15:30:00\n \"%d/%m/%Y\", # 21/03/2024\n \"%Y/%m/%d\", # 2024/03/21\n ]\n\n for fmt in formats:\n try:\n # Parse the date string\n date_obj = datetime.strptime(timestamp_str, fmt).astimezone()\n\n # If the parsed date has no timezone info, assume UTC\n if date_obj.tzinfo is None:\n date_obj = date_obj.replace(tzinfo=timezone.utc)\n\n # Convert to UTC and format\n utc_date = date_obj.astimezone(timezone.utc)\n return utc_date.strftime(\"%Y-%m-%dT%H:%M:%S.000Z\")\n except ValueError:\n continue\n\n msg = f\"Could not parse date: {timestamp_str}\"\n logger.error(msg)\n raise ValueError(msg)\n\n def astra_rest(self, args):\n headers = {\"Accept\": \"application/json\", \"X-Cassandra-Token\": f\"{self.token}\"}\n astra_url = f\"{self.get_api_endpoint()}/api/rest/v2/keyspaces/{self.get_keyspace()}/{self.collection_name}/\"\n where = {}\n\n for param in self.tools_params:\n field_name = param[\"field_name\"] if param[\"field_name\"] else param[\"name\"]\n field_value = None\n\n if field_name in self.static_filters:\n field_value = self.static_filters[field_name]\n elif param[\"name\"] in args:\n field_value = args[param[\"name\"]]\n\n if field_value is None:\n continue\n\n if param[\"is_timestamp\"] == True: # noqa: E712\n try:\n field_value = self.parse_timestamp(field_value)\n except ValueError as e:\n msg = f\"Error parsing timestamp: {e} - Use the prompt to specify the date in the correct format\"\n logger.error(msg)\n raise ValueError(msg) from e\n\n if param[\"operator\"] == \"$exists\":\n where[field_name] = {**where.get(field_name, {}), param[\"operator\"]: True}\n elif param[\"operator\"] in [\"$in\", \"$nin\", \"$all\"]:\n where[field_name] = {\n **where.get(field_name, {}),\n param[\"operator\"]: field_value.split(\",\") if isinstance(field_value, str) else field_value,\n }\n else:\n where[field_name] = {**where.get(field_name, {}), param[\"operator\"]: field_value}\n\n url = f\"{astra_url}?page-size={self.number_of_results}\"\n url += f\"&where={json.dumps(where)}\"\n\n if self.projection_fields != \"*\":\n url += f\"&fields={urllib.parse.quote(self.projection_fields.replace(' ', ''))}\"\n\n res = requests.request(\"GET\", url=url, headers=headers, timeout=10)\n\n if int(res.status_code) >= HTTPStatus.BAD_REQUEST:\n msg = f\"Error on Astra DB CQL Tool {self.tool_name} request: {res.text}\"\n logger.error(msg)\n raise ValueError(msg)\n\n try:\n res_data = res.json()\n return res_data[\"data\"]\n except ValueError:\n return res.status_code\n\n def create_args_schema(self) -> dict[str, BaseModel]:\n args: dict[str, tuple[Any, Field]] = {}\n\n for param in self.tools_params:\n field_name = param[\"field_name\"] if param[\"field_name\"] else param[\"name\"]\n if field_name not in self.static_filters:\n if param[\"mandatory\"]:\n args[param[\"name\"]] = (str, Field(description=param[\"description\"]))\n else:\n args[param[\"name\"]] = (str | None, Field(description=param[\"description\"], default=None))\n\n model = create_model(\"ToolInput\", **args, __base__=BaseModel)\n return {\"ToolInput\": model}\n\n def build_tool(self) -> Tool:\n \"\"\"Builds a Astra DB CQL Table tool.\n\n Args:\n name (str, optional): The name of the tool.\n\n Returns:\n Tool: The built Astra DB tool.\n \"\"\"\n schema_dict = self.create_args_schema()\n return StructuredTool.from_function(\n name=self.tool_name,\n args_schema=schema_dict[\"ToolInput\"],\n description=self.tool_description,\n func=self.run_model,\n return_direct=False,\n )\n\n def projection_args(self, input_str: str) -> dict:\n elements = input_str.split(\",\")\n result = {}\n\n for element in elements:\n if element.startswith(\"!\"):\n result[element[1:]] = False\n else:\n result[element] = True\n\n return result\n\n def run_model(self, **args) -> Data | list[Data]:\n results = self.astra_rest(args)\n data: list[Data] = []\n\n if isinstance(results, list):\n data = [Data(data=doc) for doc in results]\n else:\n self.status = results\n return []\n\n self.status = data\n return data\n"},"collection_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several seconds for creation to complete.","display_name":"Create new collection","field_order":["01_new_collection_name","02_embedding_generation_provider","03_embedding_generation_model","04_dimension"],"name":"create_collection","template":{"01_new_collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new collection to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_collection_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_embedding_generation_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding generation method","dynamic":false,"external_options":{},"helper_text":"To create collections with more embedding provider options, go to your database in Astra DB","info":"Provider to use for generating embeddings.","name":"embedding_generation_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_embedding_generation_model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding model","dynamic":false,"external_options":{},"info":"Model to use for generating embeddings.","name":"embedding_generation_model","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"04_dimension":{"_input_type":"IntInput","advanced":false,"display_name":"Dimensions","dynamic":false,"info":"Dimensions of the embeddings to generate.","list":false,"list_add_label":"Add More","name":"dimension","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int"}}}}},"functionality":"create"},"display_name":"Collection","dynamic":false,"external_options":{},"info":"The name of the collection within Astra DB where the vectors will be stored.","name":"collection_name","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"refresh_button":true,"required":true,"show":false,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"database_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several minutes for creation to complete.","display_name":"Create new database","field_order":["01_new_database_name","02_cloud_provider","03_region"],"name":"create_database","template":{"01_new_database_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new database to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_database_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_cloud_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Cloud provider","dynamic":false,"external_options":{},"info":"Cloud provider for the new database.","name":"cloud_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_region":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region","dynamic":false,"external_options":{},"info":"Region for the new database.","name":"region","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""}}}}},"functionality":"create"},"display_name":"Database","dynamic":false,"external_options":{},"info":"The Database name for the Astra DB instance.","name":"database_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":""},"environment":{"_input_type":"DropdownInput","advanced":true,"combobox":true,"dialog_inputs":{},"display_name":"Environment","dynamic":false,"external_options":{},"info":"The environment for the Astra DB API Endpoint.","name":"environment","options":["prod","test","dev"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"prod"},"keyspace":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Keyspace","dynamic":false,"external_options":{},"info":"Optional keyspace within Astra DB to use for the collection.","name":"keyspace","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":5},"partition_keys":{"_input_type":"DictInput","advanced":true,"display_name":"DEPRECATED: Partition Keys","dynamic":false,"info":"Field name and description to the model","list":true,"list_add_label":"Add More","name":"partition_keys","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"projection_fields":{"_input_type":"StrInput","advanced":true,"display_name":"Projection fields","dynamic":false,"info":"Attributes to return separated by comma.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"projection_fields","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"*"},"static_filters":{"_input_type":"DictInput","advanced":true,"display_name":"Static Filters","dynamic":false,"info":"Field name and value. When filled, it will not be generated by the LLM.","list":true,"list_add_label":"Add More","name":"static_filters","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Astra DB Application Token","dynamic":false,"info":"Authentication token for accessing Astra DB.","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"ASTRA_DB_APPLICATION_TOKEN"},"tool_description":{"_input_type":"StrInput","advanced":false,"display_name":"Tool Description","dynamic":false,"info":"The tool description to be passed to the model.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tool_description","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tool_name":{"_input_type":"StrInput","advanced":false,"display_name":"Tool Name","dynamic":false,"info":"The name of the tool.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tool_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tools_params":{"_input_type":"TableInput","advanced":false,"display_name":"Tools Parameters","dynamic":false,"info":"Define the structure for the tool parameters. Describe the parameters in a way the LLM can understand how to use them. Add the parameters respecting the table schema (Partition Keys, Clustering Keys and Indexed Fields).","is_list":true,"list_add_label":"Add More","name":"tools_params","override_skip":false,"placeholder":"","required":false,"show":true,"table_icon":"Table","table_schema":[{"default":"field","description":"Name of the field/parameter to be used by the model.","display_name":"Name","edit_mode":"inline","name":"name","type":"str"},{"default":"","description":"Specify the column name to be filtered on the table. Leave empty if the attribute name is the same as the name of the field.","display_name":"Field Name","edit_mode":"inline","name":"field_name","type":"str"},{"default":"description of tool parameter","description":"Describe the purpose of the parameter.","display_name":"Description","edit_mode":"popover","name":"description","type":"str"},{"default":"False","description":"Indicate if the field is mandatory.","display_name":"Is Mandatory","edit_mode":"inline","name":"mandatory","options":["True","False"],"type":"boolean"},{"default":"False","description":"Indicate if the field is a timestamp.","display_name":"Is Timestamp","edit_mode":"inline","name":"is_timestamp","options":["True","False"],"type":"boolean"},{"default":"$eq","description":"Set the operator for the field. https://docs.datastax.com/en/astra-db-serverless/api-reference/documents.html#operators","display_name":"Operator","edit_mode":"inline","name":"operator","options":["$gt","$gte","$lt","$lte","$eq","$ne","$in","$nin","$exists","$all","$size"],"type":"str"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","value":[]}},"tool_mode":false},"AstraDBChatMemory":{"base_classes":["Memory"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Retrieves and stores chat messages from Astra DB.","display_name":"Astra DB Chat Memory","documentation":"","edited":false,"field_order":["token","environment","database_name","api_endpoint","keyspace","collection_name","autodetect_collection","session_id"],"frozen":false,"icon":"AstraDB","legacy":false,"metadata":{"code_hash":"bafc81f78c76","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_astradb","version":"0.6.1"}],"total_dependencies":2},"module":"lfx.components.datastax.astradb_chatmemory.AstraDBChatMemory"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Memory","group_outputs":false,"method":"build_message_history","name":"memory","selected":"Memory","tool_mode":true,"types":["Memory"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_endpoint":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Astra DB API Endpoint","dynamic":false,"external_options":{},"info":"The API Endpoint for the Astra DB instance. Supercedes database selection.","name":"api_endpoint","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"autodetect_collection":{"_input_type":"BoolInput","advanced":true,"display_name":"Autodetect Collection","dynamic":false,"info":"Boolean flag to determine whether to autodetect the collection.","list":false,"list_add_label":"Add More","name":"autodetect_collection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from lfx.base.datastax.astradb_base import AstraDBBaseComponent\nfrom lfx.base.memory.model import LCChatMemoryComponent\nfrom lfx.field_typing.constants import Memory\nfrom lfx.inputs.inputs import MessageTextInput\n\n\nclass AstraDBChatMemory(AstraDBBaseComponent, LCChatMemoryComponent):\n display_name = \"Astra DB Chat Memory\"\n description = \"Retrieves and stores chat messages from Astra DB.\"\n name = \"AstraDBChatMemory\"\n icon: str = \"AstraDB\"\n\n inputs = [\n *AstraDBBaseComponent.inputs,\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n ]\n\n def build_message_history(self) -> Memory:\n try:\n from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `uv pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n return AstraDBChatMessageHistory(\n session_id=self.session_id,\n collection_name=self.collection_name,\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.get_keyspace(),\n environment=self.environment,\n )\n"},"collection_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several seconds for creation to complete.","display_name":"Create new collection","field_order":["01_new_collection_name","02_embedding_generation_provider","03_embedding_generation_model","04_dimension"],"name":"create_collection","template":{"01_new_collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new collection to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_collection_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_embedding_generation_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding generation method","dynamic":false,"external_options":{},"helper_text":"To create collections with more embedding provider options, go to your database in Astra DB","info":"Provider to use for generating embeddings.","name":"embedding_generation_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_embedding_generation_model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding model","dynamic":false,"external_options":{},"info":"Model to use for generating embeddings.","name":"embedding_generation_model","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"04_dimension":{"_input_type":"IntInput","advanced":false,"display_name":"Dimensions","dynamic":false,"info":"Dimensions of the embeddings to generate.","list":false,"list_add_label":"Add More","name":"dimension","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int"}}}}},"functionality":"create"},"display_name":"Collection","dynamic":false,"external_options":{},"info":"The name of the collection within Astra DB where the vectors will be stored.","name":"collection_name","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"refresh_button":true,"required":true,"show":false,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"database_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several minutes for creation to complete.","display_name":"Create new database","field_order":["01_new_database_name","02_cloud_provider","03_region"],"name":"create_database","template":{"01_new_database_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new database to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_database_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_cloud_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Cloud provider","dynamic":false,"external_options":{},"info":"Cloud provider for the new database.","name":"cloud_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_region":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region","dynamic":false,"external_options":{},"info":"Region for the new database.","name":"region","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""}}}}},"functionality":"create"},"display_name":"Database","dynamic":false,"external_options":{},"info":"The Database name for the Astra DB instance.","name":"database_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":""},"environment":{"_input_type":"DropdownInput","advanced":true,"combobox":true,"dialog_inputs":{},"display_name":"Environment","dynamic":false,"external_options":{},"info":"The environment for the Astra DB API Endpoint.","name":"environment","options":["prod","test","dev"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"prod"},"keyspace":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Keyspace","dynamic":false,"external_options":{},"info":"Optional keyspace within Astra DB to use for the collection.","name":"keyspace","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"session_id":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Session ID","dynamic":false,"info":"The session ID of the chat. If empty, the current session ID parameter will be used.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"session_id","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":""},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Astra DB Application Token","dynamic":false,"info":"Authentication token for accessing Astra DB.","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"ASTRA_DB_APPLICATION_TOKEN"}},"tool_mode":false},"AstraDBGraph":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Implementation of Graph Vector Store using Astra DB","display_name":"Astra DB Graph","documentation":"https://docs.langflow.org/bundles-datastax","edited":false,"field_order":["token","environment","database_name","api_endpoint","keyspace","collection_name","autodetect_collection","ingest_data","search_query","should_cache_vector_store","metadata_incoming_links_key","number_of_results","search_type","search_score_threshold","search_filter"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"9f5d576b30ca","dependencies":{"dependencies":[{"name":"orjson","version":"3.10.15"},{"name":"lfx","version":null},{"name":"langchain_astradb","version":"0.6.1"}],"total_dependencies":3},"module":"lfx.components.datastax.astradb_graph.AstraDBGraphVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":["datastax.GraphRAG"],"template":{"_type":"Component","api_endpoint":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Astra DB API Endpoint","dynamic":false,"external_options":{},"info":"The API Endpoint for the Astra DB instance. Supercedes database selection.","name":"api_endpoint","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"autodetect_collection":{"_input_type":"BoolInput","advanced":true,"display_name":"Autodetect Collection","dynamic":false,"info":"Boolean flag to determine whether to autodetect the collection.","list":false,"list_add_label":"Add More","name":"autodetect_collection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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 orjson\n\nfrom lfx.base.datastax.astradb_base import AstraDBBaseComponent\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.inputs.inputs import (\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n StrInput,\n)\nfrom lfx.schema.data import Data\n\n\nclass AstraDBGraphVectorStoreComponent(AstraDBBaseComponent, LCVectorStoreComponent):\n display_name: str = \"Astra DB Graph\"\n description: str = \"Implementation of Graph Vector Store using Astra DB\"\n name = \"AstraDBGraph\"\n documentation: str = \"https://docs.langflow.org/bundles-datastax\"\n icon: str = \"AstraDB\"\n legacy: bool = True\n replacement = [\"datastax.GraphRAG\"]\n\n inputs = [\n *AstraDBBaseComponent.inputs,\n *LCVectorStoreComponent.inputs,\n StrInput(\n name=\"metadata_incoming_links_key\",\n display_name=\"Metadata incoming links key\",\n info=\"Metadata key used for incoming links.\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\n \"Similarity\",\n \"Similarity with score threshold\",\n \"MMR (Max Marginal Relevance)\",\n \"Graph Traversal\",\n \"MMR (Max Marginal Relevance) Graph Traversal\",\n ],\n value=\"MMR (Max Marginal Relevance) Graph Traversal\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n is_list=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBGraphVectorStore\n from langchain_astradb.utils.astradb import SetupMode\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n try:\n if not self.setup_mode:\n self.setup_mode = self._inputs[\"setup_mode\"].options[0]\n\n setup_mode_value = SetupMode[self.setup_mode.upper()]\n except KeyError as e:\n msg = f\"Invalid setup mode: {self.setup_mode}\"\n raise ValueError(msg) from e\n\n try:\n self.log(f\"Initializing Graph Vector Store {self.collection_name}\")\n\n vector_store = AstraDBGraphVectorStore(\n embedding=self.embedding_model,\n collection_name=self.collection_name,\n metadata_incoming_links_key=self.metadata_incoming_links_key or \"incoming_links\",\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.get_keyspace(),\n environment=self.environment,\n metric=self.metric or None,\n batch_size=self.batch_size or None,\n bulk_insert_batch_concurrency=self.bulk_insert_batch_concurrency or None,\n bulk_insert_overwrite_concurrency=self.bulk_insert_overwrite_concurrency or None,\n bulk_delete_concurrency=self.bulk_delete_concurrency or None,\n setup_mode=setup_mode_value,\n pre_delete_collection=self.pre_delete_collection,\n metadata_indexing_include=[s for s in self.metadata_indexing_include if s] or None,\n metadata_indexing_exclude=[s for s in self.metadata_indexing_exclude if s] or None,\n collection_indexing_policy=orjson.loads(self.collection_indexing_policy.encode(\"utf-8\"))\n if self.collection_indexing_policy\n else None,\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Vector Store initialized: {vector_store.astra_env.collection_name}\")\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n match self.search_type:\n case \"Similarity\":\n return \"similarity\"\n case \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n case \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n case \"Graph Traversal\":\n return \"traversal\"\n case \"MMR (Max Marginal Relevance) Graph Traversal\":\n return \"mmr_traversal\"\n case _:\n return \"similarity\"\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n if not vector_store:\n vector_store = self.build_vector_store()\n\n self.log(\"Searching for documents in AstraDBGraphVectorStore.\")\n self.log(f\"Search query: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n\n # Drop links from the metadata. At this point the links don't add any value for building the\n # context and haven't been restored to json which causes the conversion to fail.\n self.log(\"Removing links from metadata.\")\n for doc in docs:\n if \"links\" in doc.metadata:\n doc.metadata.pop(\"links\")\n\n except Exception as e:\n msg = f\"Error performing search in AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n\n self.log(f\"Converted documents to data: {len(data)}\")\n\n self.status = data\n return data\n self.log(\"No search input provided. Skipping search.\")\n return []\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"},"collection_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several seconds for creation to complete.","display_name":"Create new collection","field_order":["01_new_collection_name","02_embedding_generation_provider","03_embedding_generation_model","04_dimension"],"name":"create_collection","template":{"01_new_collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new collection to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_collection_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_embedding_generation_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding generation method","dynamic":false,"external_options":{},"helper_text":"To create collections with more embedding provider options, go to your database in Astra DB","info":"Provider to use for generating embeddings.","name":"embedding_generation_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_embedding_generation_model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding model","dynamic":false,"external_options":{},"info":"Model to use for generating embeddings.","name":"embedding_generation_model","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"04_dimension":{"_input_type":"IntInput","advanced":false,"display_name":"Dimensions","dynamic":false,"info":"Dimensions of the embeddings to generate.","list":false,"list_add_label":"Add More","name":"dimension","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int"}}}}},"functionality":"create"},"display_name":"Collection","dynamic":false,"external_options":{},"info":"The name of the collection within Astra DB where the vectors will be stored.","name":"collection_name","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"refresh_button":true,"required":true,"show":false,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"database_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several minutes for creation to complete.","display_name":"Create new database","field_order":["01_new_database_name","02_cloud_provider","03_region"],"name":"create_database","template":{"01_new_database_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new database to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_database_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_cloud_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Cloud provider","dynamic":false,"external_options":{},"info":"Cloud provider for the new database.","name":"cloud_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_region":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region","dynamic":false,"external_options":{},"info":"Region for the new database.","name":"region","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""}}}}},"functionality":"create"},"display_name":"Database","dynamic":false,"external_options":{},"info":"The Database name for the Astra DB instance.","name":"database_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":""},"environment":{"_input_type":"DropdownInput","advanced":true,"combobox":true,"dialog_inputs":{},"display_name":"Environment","dynamic":false,"external_options":{},"info":"The environment for the Astra DB API Endpoint.","name":"environment","options":["prod","test","dev"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"prod"},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"keyspace":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Keyspace","dynamic":false,"external_options":{},"info":"Optional keyspace within Astra DB to use for the collection.","name":"keyspace","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"metadata_incoming_links_key":{"_input_type":"StrInput","advanced":true,"display_name":"Metadata incoming links key","dynamic":false,"info":"Metadata key used for incoming links.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"metadata_incoming_links_key","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"search_filter":{"_input_type":"DictInput","advanced":true,"display_name":"Search Metadata Filter","dynamic":false,"info":"Optional dictionary of filters to apply to the search query.","list":true,"list_add_label":"Add More","name":"search_filter","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"search_score_threshold":{"_input_type":"FloatInput","advanced":true,"display_name":"Search Score Threshold","dynamic":false,"info":"Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')","list":false,"list_add_label":"Add More","name":"search_score_threshold","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.0},"search_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Type","dynamic":false,"external_options":{},"info":"Search type to use","name":"search_type","options":["Similarity","Similarity with score threshold","MMR (Max Marginal Relevance)","Graph Traversal","MMR (Max Marginal Relevance) Graph Traversal"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"MMR (Max Marginal Relevance) Graph Traversal"},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Astra DB Application Token","dynamic":false,"info":"Authentication token for accessing Astra DB.","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"ASTRA_DB_APPLICATION_TOKEN"}},"tool_mode":false},"AstraDBTool":{"base_classes":["Data","Tool"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Tool to run hybrid vector and metadata search on DataStax Astra DB Collection","display_name":"Astra DB Tool","documentation":"https://docs.langflow.org/bundles-datastax","edited":false,"field_order":["token","environment","database_name","api_endpoint","keyspace","collection_name","autodetect_collection","tool_name","tool_description","projection_attributes","tools_params_v2","tool_params","static_filters","number_of_results","use_search_query","use_vectorize","semantic_search_instruction"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"44719b6ed1a3","dependencies":{"dependencies":[{"name":"astrapy","version":"2.1.0"},{"name":"langchain_core","version":"0.3.80"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.datastax.astradb_tool.AstraDBToolComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Data","group_outputs":false,"method":"run_model","name":"api_run_model","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Tool","group_outputs":false,"method":"build_tool","name":"api_build_tool","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":["datastax.AstraDB"],"template":{"_type":"Component","api_endpoint":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Astra DB API Endpoint","dynamic":false,"external_options":{},"info":"The API Endpoint for the Astra DB instance. Supercedes database selection.","name":"api_endpoint","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"autodetect_collection":{"_input_type":"BoolInput","advanced":true,"display_name":"Autodetect Collection","dynamic":false,"info":"Boolean flag to determine whether to autodetect the collection.","list":false,"list_add_label":"Add More","name":"autodetect_collection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from datetime import datetime, timezone\nfrom typing import Any\n\nfrom astrapy import Collection, DataAPIClient, Database\nfrom langchain_core.tools import StructuredTool, Tool\nfrom pydantic import BaseModel, Field, create_model\n\nfrom lfx.base.datastax.astradb_base import AstraDBBaseComponent\nfrom lfx.base.langchain_utilities.model import LCToolComponent\nfrom lfx.io import BoolInput, DictInput, IntInput, StrInput, TableInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.schema.table import EditMode\n\n\nclass AstraDBToolComponent(AstraDBBaseComponent, LCToolComponent):\n display_name: str = \"Astra DB Tool\"\n description: str = \"Tool to run hybrid vector and metadata search on DataStax Astra DB Collection\"\n documentation: str = \"https://docs.langflow.org/bundles-datastax\"\n icon: str = \"AstraDB\"\n legacy: bool = True\n name = \"AstraDBTool\"\n replacement = [\"datastax.AstraDB\"]\n\n inputs = [\n *AstraDBBaseComponent.inputs,\n StrInput(\n name=\"tool_name\",\n display_name=\"Tool Name\",\n info=\"The name of the tool to be passed to the LLM.\",\n required=True,\n ),\n StrInput(\n name=\"tool_description\",\n display_name=\"Tool Description\",\n info=\"Describe the tool to LLM. Add any information that can help the LLM to use the tool.\",\n required=True,\n ),\n StrInput(\n name=\"projection_attributes\",\n display_name=\"Projection Attributes\",\n info=\"Attributes to be returned by the tool separated by comma.\",\n required=True,\n value=\"*\",\n advanced=True,\n ),\n TableInput(\n name=\"tools_params_v2\",\n display_name=\"Tools Parameters\",\n info=\"Define the structure for the tool parameters. Describe the parameters \"\n \"in a way the LLM can understand how to use them.\",\n required=False,\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field/parameter for the model.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"attribute_name\",\n \"display_name\": \"Attribute Name\",\n \"type\": \"str\",\n \"description\": \"Specify the attribute name to be filtered on the collection. \"\n \"Leave empty if the attribute name is the same as the name of the field.\",\n \"default\": \"\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"metadata\",\n \"display_name\": \"Is Metadata\",\n \"type\": \"boolean\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate if the field is included in the metadata field.\"),\n \"options\": [\"True\", \"False\"],\n \"default\": \"False\",\n },\n {\n \"name\": \"mandatory\",\n \"display_name\": \"Is Mandatory\",\n \"type\": \"boolean\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate if the field is mandatory.\"),\n \"options\": [\"True\", \"False\"],\n \"default\": \"False\",\n },\n {\n \"name\": \"is_timestamp\",\n \"display_name\": \"Is Timestamp\",\n \"type\": \"boolean\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\"Indicate if the field is a timestamp.\"),\n \"options\": [\"True\", \"False\"],\n \"default\": \"False\",\n },\n {\n \"name\": \"operator\",\n \"display_name\": \"Operator\",\n \"type\": \"str\",\n \"description\": \"Set the operator for the field. \"\n \"https://docs.datastax.com/en/astra-db-serverless/api-reference/documents.html#operators\",\n \"default\": \"$eq\",\n \"options\": [\"$gt\", \"$gte\", \"$lt\", \"$lte\", \"$eq\", \"$ne\", \"$in\", \"$nin\", \"$exists\", \"$all\", \"$size\"],\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[],\n ),\n DictInput(\n name=\"tool_params\",\n info=\"DEPRECATED: Attributes to filter and description to the model. \"\n \"Add ! for mandatory (e.g: !customerId)\",\n display_name=\"Tool params\",\n is_list=True,\n advanced=True,\n ),\n DictInput(\n name=\"static_filters\",\n info=\"Attributes to filter and correspoding value\",\n display_name=\"Static filters\",\n advanced=True,\n is_list=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=5,\n ),\n BoolInput(\n name=\"use_search_query\",\n display_name=\"Semantic Search\",\n info=\"When this parameter is activated, the search query parameter will be used to search the collection.\",\n advanced=False,\n value=False,\n ),\n BoolInput(\n name=\"use_vectorize\",\n display_name=\"Use Astra DB Vectorize\",\n info=\"When this parameter is activated, Astra DB Vectorize method will be used to generate the embeddings.\",\n advanced=False,\n value=False,\n ),\n StrInput(\n name=\"semantic_search_instruction\",\n display_name=\"Semantic Search Instruction\",\n info=\"The instruction to use for the semantic search.\",\n required=True,\n value=\"Search query to find relevant documents.\",\n advanced=True,\n ),\n ]\n\n _cached_client: DataAPIClient | None = None\n _cached_db: Database | None = None\n _cached_collection: Collection | None = None\n\n def create_args_schema(self) -> dict[str, BaseModel]:\n \"\"\"DEPRECATED: This method is deprecated. Please use create_args_schema_v2 instead.\n\n It is keep only for backward compatibility.\n \"\"\"\n logger.warning(\"This is the old way to define the tool parameters. Please use the new way.\")\n args: dict[str, tuple[Any, Field] | list[str]] = {}\n\n for key in self.tool_params:\n if key.startswith(\"!\"): # Mandatory\n args[key[1:]] = (str, Field(description=self.tool_params[key]))\n else: # Optional\n args[key] = (str | None, Field(description=self.tool_params[key], default=None))\n\n if self.use_search_query:\n args[\"search_query\"] = (\n str | None,\n Field(description=\"Search query to find relevant documents.\", default=None),\n )\n\n model = create_model(\"ToolInput\", **args, __base__=BaseModel)\n return {\"ToolInput\": model}\n\n def create_args_schema_v2(self) -> dict[str, BaseModel]:\n \"\"\"Create the tool input schema using the new tool parameters configuration.\"\"\"\n args: dict[str, tuple[Any, Field] | list[str]] = {}\n\n for tool_param in self.tools_params_v2:\n if tool_param[\"mandatory\"]:\n args[tool_param[\"name\"]] = (str, Field(description=tool_param[\"description\"]))\n else:\n args[tool_param[\"name\"]] = (str | None, Field(description=tool_param[\"description\"], default=None))\n\n if self.use_search_query:\n args[\"search_query\"] = (\n str,\n Field(description=self.semantic_search_instruction),\n )\n\n model = create_model(\"ToolInput\", **args, __base__=BaseModel)\n return {\"ToolInput\": model}\n\n def build_tool(self) -> Tool:\n \"\"\"Builds an Astra DB Collection tool.\n\n Returns:\n Tool: The built Astra DB tool.\n \"\"\"\n schema_dict = self.create_args_schema() if len(self.tool_params.keys()) > 0 else self.create_args_schema_v2()\n\n tool = StructuredTool.from_function(\n name=self.tool_name,\n args_schema=schema_dict[\"ToolInput\"],\n description=self.tool_description,\n func=self.run_model,\n return_direct=False,\n )\n self.status = \"Astra DB Tool created\"\n\n return tool\n\n def projection_args(self, input_str: str) -> dict | None:\n \"\"\"Build the projection arguments for the Astra DB query.\"\"\"\n elements = input_str.split(\",\")\n result = {}\n\n if elements == [\"*\"]:\n return None\n\n # Force the projection to exclude the $vector field as it is not required by the tool\n result[\"$vector\"] = False\n\n # Fields with ! as prefix should be removed from the projection\n for element in elements:\n if element.startswith(\"!\"):\n result[element[1:]] = False\n else:\n result[element] = True\n\n return result\n\n def parse_timestamp(self, timestamp_str: str) -> datetime:\n \"\"\"Parse a timestamp string into Astra DB REST API format.\n\n Args:\n timestamp_str (str): Input timestamp string\n\n Returns:\n datetime: Datetime object\n\n Raises:\n ValueError: If the timestamp cannot be parsed\n \"\"\"\n # Common datetime formats to try\n formats = [\n \"%Y-%m-%d\", # 2024-03-21\n \"%Y-%m-%dT%H:%M:%S\", # 2024-03-21T15:30:00\n \"%Y-%m-%dT%H:%M:%S%z\", # 2024-03-21T15:30:00+0000\n \"%Y-%m-%d %H:%M:%S\", # 2024-03-21 15:30:00\n \"%d/%m/%Y\", # 21/03/2024\n \"%Y/%m/%d\", # 2024/03/21\n ]\n\n for fmt in formats:\n try:\n # Parse the date string\n date_obj = datetime.strptime(timestamp_str, fmt).astimezone()\n\n # If the parsed date has no timezone info, assume UTC\n if date_obj.tzinfo is None:\n date_obj = date_obj.replace(tzinfo=timezone.utc)\n\n # Convert to UTC and format\n return date_obj.astimezone(timezone.utc)\n\n except ValueError:\n continue\n\n msg = f\"Could not parse date: {timestamp_str}\"\n logger.error(msg)\n raise ValueError(msg)\n\n def build_filter(self, args: dict, filter_settings: list) -> dict:\n \"\"\"Build filter dictionary for Astra DB query.\n\n Args:\n args: Dictionary of arguments from the tool\n filter_settings: List of filter settings from tools_params_v2\n Returns:\n Dictionary containing the filter conditions\n \"\"\"\n filters = {**self.static_filters}\n\n for key, value in args.items():\n # Skip search_query as it's handled separately\n if key == \"search_query\":\n continue\n\n filter_setting = next((x for x in filter_settings if x[\"name\"] == key), None)\n if filter_setting and value is not None:\n field_name = filter_setting[\"attribute_name\"] if filter_setting[\"attribute_name\"] else key\n filter_key = field_name if not filter_setting[\"metadata\"] else f\"metadata.{field_name}\"\n if filter_setting[\"operator\"] == \"$exists\":\n filters[filter_key] = {**filters.get(filter_key, {}), filter_setting[\"operator\"]: True}\n elif filter_setting[\"operator\"] in [\"$in\", \"$nin\", \"$all\"]:\n filters[filter_key] = {\n **filters.get(filter_key, {}),\n filter_setting[\"operator\"]: value.split(\",\") if isinstance(value, str) else value,\n }\n elif filter_setting[\"is_timestamp\"] == True: # noqa: E712\n try:\n filters[filter_key] = {\n **filters.get(filter_key, {}),\n filter_setting[\"operator\"]: self.parse_timestamp(value),\n }\n except ValueError as e:\n msg = f\"Error parsing timestamp: {e} - Use the prompt to specify the date in the correct format\"\n logger.error(msg)\n raise ValueError(msg) from e\n else:\n filters[filter_key] = {**filters.get(filter_key, {}), filter_setting[\"operator\"]: value}\n return filters\n\n def run_model(self, **args) -> Data | list[Data]:\n \"\"\"Run the query to get the data from the Astra DB collection.\"\"\"\n sort = {}\n\n # Build filters using the new method\n filters = self.build_filter(args, self.tools_params_v2)\n\n # Build the vector search on\n if self.use_search_query and args[\"search_query\"] is not None and args[\"search_query\"] != \"\":\n if self.use_vectorize:\n sort[\"$vectorize\"] = args[\"search_query\"]\n else:\n if self.embedding is None:\n msg = \"Embedding model is not set. Please set the embedding model or use Astra DB Vectorize.\"\n logger.error(msg)\n raise ValueError(msg)\n embedding_query = self.embedding.embed_query(args[\"search_query\"])\n sort[\"$vector\"] = embedding_query\n del args[\"search_query\"]\n\n find_options = {\n \"filter\": filters,\n \"limit\": self.number_of_results,\n \"sort\": sort,\n }\n\n projection = self.projection_args(self.projection_attributes)\n if projection and len(projection) > 0:\n find_options[\"projection\"] = projection\n\n try:\n database = self.get_database_object(api_endpoint=self.get_api_endpoint())\n collection = database.get_collection(\n name=self.collection_name,\n keyspace=self.get_keyspace(),\n )\n results = collection.find(**find_options)\n except Exception as e:\n msg = f\"Error on Astra DB Tool {self.tool_name} request: {e}\"\n logger.error(msg)\n raise ValueError(msg) from e\n\n logger.info(f\"Tool {self.tool_name} executed`\")\n\n data: list[Data] = [Data(data=doc) for doc in results]\n self.status = data\n return data\n"},"collection_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several seconds for creation to complete.","display_name":"Create new collection","field_order":["01_new_collection_name","02_embedding_generation_provider","03_embedding_generation_model","04_dimension"],"name":"create_collection","template":{"01_new_collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new collection to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_collection_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_embedding_generation_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding generation method","dynamic":false,"external_options":{},"helper_text":"To create collections with more embedding provider options, go to your database in Astra DB","info":"Provider to use for generating embeddings.","name":"embedding_generation_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_embedding_generation_model":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Embedding model","dynamic":false,"external_options":{},"info":"Model to use for generating embeddings.","name":"embedding_generation_model","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"04_dimension":{"_input_type":"IntInput","advanced":false,"display_name":"Dimensions","dynamic":false,"info":"Dimensions of the embeddings to generate.","list":false,"list_add_label":"Add More","name":"dimension","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int"}}}}},"functionality":"create"},"display_name":"Collection","dynamic":false,"external_options":{},"info":"The name of the collection within Astra DB where the vectors will be stored.","name":"collection_name","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"refresh_button":true,"required":true,"show":false,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"database_name":{"_input_type":"DropdownInput","advanced":false,"combobox":true,"dialog_inputs":{"fields":{"data":{"node":{"description":"Please allow several minutes for creation to complete.","display_name":"Create new database","field_order":["01_new_database_name","02_cloud_provider","03_region"],"name":"create_database","template":{"01_new_database_name":{"_input_type":"StrInput","advanced":false,"display_name":"Name","dynamic":false,"info":"Name of the new database to create in Astra DB.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"new_database_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"02_cloud_provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Cloud provider","dynamic":false,"external_options":{},"info":"Cloud provider for the new database.","name":"cloud_provider","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"03_region":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Region","dynamic":false,"external_options":{},"info":"Region for the new database.","name":"region","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""}}}}},"functionality":"create"},"display_name":"Database","dynamic":false,"external_options":{},"info":"The Database name for the Astra DB instance.","name":"database_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":""},"environment":{"_input_type":"DropdownInput","advanced":true,"combobox":true,"dialog_inputs":{},"display_name":"Environment","dynamic":false,"external_options":{},"info":"The environment for the Astra DB API Endpoint.","name":"environment","options":["prod","test","dev"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"prod"},"keyspace":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Keyspace","dynamic":false,"external_options":{},"info":"Optional keyspace within Astra DB to use for the collection.","name":"keyspace","options":[],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":5},"projection_attributes":{"_input_type":"StrInput","advanced":true,"display_name":"Projection Attributes","dynamic":false,"info":"Attributes to be returned by the tool separated by comma.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"projection_attributes","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"*"},"semantic_search_instruction":{"_input_type":"StrInput","advanced":true,"display_name":"Semantic Search Instruction","dynamic":false,"info":"The instruction to use for the semantic search.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"semantic_search_instruction","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"Search query to find relevant documents."},"static_filters":{"_input_type":"DictInput","advanced":true,"display_name":"Static filters","dynamic":false,"info":"Attributes to filter and correspoding value","list":true,"list_add_label":"Add More","name":"static_filters","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Astra DB Application Token","dynamic":false,"info":"Authentication token for accessing Astra DB.","input_types":[],"load_from_db":true,"name":"token","override_skip":false,"password":true,"placeholder":"","real_time_refresh":true,"required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"ASTRA_DB_APPLICATION_TOKEN"},"tool_description":{"_input_type":"StrInput","advanced":false,"display_name":"Tool Description","dynamic":false,"info":"Describe the tool to LLM. Add any information that can help the LLM to use the tool.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tool_description","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tool_name":{"_input_type":"StrInput","advanced":false,"display_name":"Tool Name","dynamic":false,"info":"The name of the tool to be passed to the LLM.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"tool_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"tool_params":{"_input_type":"DictInput","advanced":true,"display_name":"Tool params","dynamic":false,"info":"DEPRECATED: Attributes to filter and description to the model. Add ! for mandatory (e.g: !customerId)","list":true,"list_add_label":"Add More","name":"tool_params","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"tools_params_v2":{"_input_type":"TableInput","advanced":false,"display_name":"Tools Parameters","dynamic":false,"info":"Define the structure for the tool parameters. Describe the parameters in a way the LLM can understand how to use them.","is_list":true,"list_add_label":"Add More","name":"tools_params_v2","override_skip":false,"placeholder":"","required":false,"show":true,"table_icon":"Table","table_schema":[{"default":"field","description":"Specify the name of the output field/parameter for the model.","display_name":"Name","edit_mode":"inline","name":"name","type":"str"},{"default":"","description":"Specify the attribute name to be filtered on the collection. Leave empty if the attribute name is the same as the name of the field.","display_name":"Attribute Name","edit_mode":"inline","name":"attribute_name","type":"str"},{"default":"description of field","description":"Describe the purpose of the output field.","display_name":"Description","edit_mode":"popover","name":"description","type":"str"},{"default":"False","description":"Indicate if the field is included in the metadata field.","display_name":"Is Metadata","edit_mode":"inline","name":"metadata","options":["True","False"],"type":"boolean"},{"default":"False","description":"Indicate if the field is mandatory.","display_name":"Is Mandatory","edit_mode":"inline","name":"mandatory","options":["True","False"],"type":"boolean"},{"default":"False","description":"Indicate if the field is a timestamp.","display_name":"Is Timestamp","edit_mode":"inline","name":"is_timestamp","options":["True","False"],"type":"boolean"},{"default":"$eq","description":"Set the operator for the field. https://docs.datastax.com/en/astra-db-serverless/api-reference/documents.html#operators","display_name":"Operator","edit_mode":"inline","name":"operator","options":["$gt","$gte","$lt","$lte","$eq","$ne","$in","$nin","$exists","$all","$size"],"type":"str"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","value":[]},"use_search_query":{"_input_type":"BoolInput","advanced":false,"display_name":"Semantic Search","dynamic":false,"info":"When this parameter is activated, the search query parameter will be used to search the collection.","list":false,"list_add_label":"Add More","name":"use_search_query","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"use_vectorize":{"_input_type":"BoolInput","advanced":false,"display_name":"Use Astra DB Vectorize","dynamic":false,"info":"When this parameter is activated, Astra DB Vectorize method will be used to generate the embeddings.","list":false,"list_add_label":"Add More","name":"use_vectorize","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"AstraVectorize":{"base_classes":["dict"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Configuration options for Astra Vectorize server-side embeddings. ","display_name":"Astra Vectorize","documentation":"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html","edited":false,"field_order":["provider","model_name","api_key_name","authentication","provider_api_key","authentication","model_parameters"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"3d976690c262","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.datastax.astradb_vectorize.AstraVectorizeComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Vectorize","group_outputs":false,"method":"build_options","name":"config","selected":"dict","tool_mode":true,"types":["dict"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":["datastax.AstraDB"],"template":{"_type":"Component","api_key_name":{"_input_type":"MessageTextInput","advanced":false,"display_name":"API Key name","dynamic":false,"info":"The name of the embeddings provider API key stored on Astra. If set, it will override the 'ProviderKey' in the authentication parameters.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"api_key_name","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":""},"authentication":{"_input_type":"DictInput","advanced":true,"display_name":"Authentication Parameters","dynamic":false,"info":"","list":true,"list_add_label":"Add More","name":"authentication","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","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":"from typing import Any\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import DictInput, DropdownInput, MessageTextInput, SecretStrInput\nfrom lfx.template.field.base import Output\n\n\nclass AstraVectorizeComponent(Component):\n display_name: str = \"Astra Vectorize\"\n description: str = \"Configuration options for Astra Vectorize server-side embeddings. \"\n documentation: str = \"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html\"\n legacy = True\n icon = \"AstraDB\"\n name = \"AstraVectorize\"\n replacement = [\"datastax.AstraDB\"]\n\n VECTORIZE_PROVIDERS_MAPPING = {\n \"Azure OpenAI\": [\"azureOpenAI\", [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]],\n \"Hugging Face - Dedicated\": [\"huggingfaceDedicated\", [\"endpoint-defined-model\"]],\n \"Hugging Face - Serverless\": [\n \"huggingface\",\n [\n \"sentence-transformers/all-MiniLM-L6-v2\",\n \"intfloat/multilingual-e5-large\",\n \"intfloat/multilingual-e5-large-instruct\",\n \"BAAI/bge-small-en-v1.5\",\n \"BAAI/bge-base-en-v1.5\",\n \"BAAI/bge-large-en-v1.5\",\n ],\n ],\n \"Jina AI\": [\n \"jinaAI\",\n [\n \"jina-embeddings-v2-base-en\",\n \"jina-embeddings-v2-base-de\",\n \"jina-embeddings-v2-base-es\",\n \"jina-embeddings-v2-base-code\",\n \"jina-embeddings-v2-base-zh\",\n ],\n ],\n \"Mistral AI\": [\"mistral\", [\"mistral-embed\"]],\n \"NVIDIA\": [\"nvidia\", [\"NV-Embed-QA\"]],\n \"OpenAI\": [\"openai\", [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]],\n \"Upstage\": [\"upstageAI\", [\"solar-embedding-1-large\"]],\n \"Voyage AI\": [\n \"voyageAI\",\n [\"voyage-large-2-instruct\", \"voyage-law-2\", \"voyage-code-2\", \"voyage-large-2\", \"voyage-2\"],\n ],\n }\n VECTORIZE_MODELS_STR = \"\\n\\n\".join(\n [provider + \": \" + (\", \".join(models[1])) for provider, models in VECTORIZE_PROVIDERS_MAPPING.items()]\n )\n\n inputs = [\n DropdownInput(\n name=\"provider\",\n display_name=\"Provider\",\n options=VECTORIZE_PROVIDERS_MAPPING.keys(),\n value=\"\",\n required=True,\n ),\n MessageTextInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n info=\"The embedding model to use for the selected provider. Each provider has a different set of models \"\n f\"available (full list at https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\\n\\n{VECTORIZE_MODELS_STR}\",\n required=True,\n ),\n MessageTextInput(\n name=\"api_key_name\",\n display_name=\"API Key name\",\n info=\"The name of the embeddings provider API key stored on Astra. \"\n \"If set, it will override the 'ProviderKey' in the authentication parameters.\",\n ),\n DictInput(\n name=\"authentication\",\n display_name=\"Authentication parameters\",\n is_list=True,\n advanced=True,\n ),\n SecretStrInput(\n name=\"provider_api_key\",\n display_name=\"Provider API Key\",\n info=\"An alternative to the Astra Authentication that passes an API key for the provider with each request \"\n \"to Astra DB. \"\n \"This may be used when Vectorize is configured for the collection, \"\n \"but no corresponding provider secret is stored within Astra's key management system.\",\n advanced=True,\n ),\n DictInput(\n name=\"authentication\",\n display_name=\"Authentication Parameters\",\n is_list=True,\n advanced=True,\n ),\n DictInput(\n name=\"model_parameters\",\n display_name=\"Model Parameters\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Vectorize\", name=\"config\", method=\"build_options\", types=[\"dict\"]),\n ]\n\n def build_options(self) -> dict[str, Any]:\n provider_value = self.VECTORIZE_PROVIDERS_MAPPING[self.provider][0]\n authentication = {**(self.authentication or {})}\n api_key_name = self.api_key_name\n if api_key_name:\n authentication[\"providerKey\"] = api_key_name\n return {\n # must match astrapy.info.VectorServiceOptions\n \"collection_vector_service_options\": {\n \"provider\": provider_value,\n \"modelName\": self.model_name,\n \"authentication\": authentication,\n \"parameters\": self.model_parameters or {},\n },\n \"collection_embedding_api_key\": self.provider_api_key,\n }\n"},"model_name":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Model Name","dynamic":false,"info":"The embedding model to use for the selected provider. Each provider has a different set of models available (full list at https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\n\nAzure OpenAI: text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002\n\nHugging Face - Dedicated: endpoint-defined-model\n\nHugging Face - Serverless: sentence-transformers/all-MiniLM-L6-v2, intfloat/multilingual-e5-large, intfloat/multilingual-e5-large-instruct, BAAI/bge-small-en-v1.5, BAAI/bge-base-en-v1.5, BAAI/bge-large-en-v1.5\n\nJina AI: jina-embeddings-v2-base-en, jina-embeddings-v2-base-de, jina-embeddings-v2-base-es, jina-embeddings-v2-base-code, jina-embeddings-v2-base-zh\n\nMistral AI: mistral-embed\n\nNVIDIA: NV-Embed-QA\n\nOpenAI: text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002\n\nUpstage: solar-embedding-1-large\n\nVoyage AI: voyage-large-2-instruct, voyage-law-2, voyage-code-2, voyage-large-2, voyage-2","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"model_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"model_parameters":{"_input_type":"DictInput","advanced":true,"display_name":"Model Parameters","dynamic":false,"info":"","list":true,"list_add_label":"Add More","name":"model_parameters","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"provider":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Provider","dynamic":false,"external_options":{},"info":"","name":"provider","options":["Azure OpenAI","Hugging Face - Dedicated","Hugging Face - Serverless","Jina AI","Mistral AI","NVIDIA","OpenAI","Upstage","Voyage AI"],"options_metadata":[],"override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"provider_api_key":{"_input_type":"SecretStrInput","advanced":true,"display_name":"Provider API Key","dynamic":false,"info":"An alternative to the Astra Authentication that passes an API key for the provider with each request to Astra DB. This may be used when Vectorize is configured for the collection, but no corresponding provider secret is stored within Astra's key management system.","input_types":[],"load_from_db":true,"name":"provider_api_key","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"Dotenv":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Load .env file into env vars","display_name":"Dotenv","documentation":"","edited":false,"field_order":["dotenv_file_content"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"343ea9aaca1b","dependencies":{"dependencies":[{"name":"dotenv","version":"1.2.1"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.datastax.dotenv.Dotenv"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"env_set","group_outputs":false,"method":"process_inputs","name":"env_set","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 io\n\nfrom dotenv import load_dotenv\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import MultilineSecretInput\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\n\n\nclass Dotenv(Component):\n display_name = \"Dotenv\"\n description = \"Load .env file into env vars\"\n icon = \"AstraDB\"\n legacy = True\n inputs = [\n MultilineSecretInput(\n name=\"dotenv_file_content\",\n display_name=\"Dotenv file content\",\n info=\"Paste the content of your .env file directly, since contents are sensitive, \"\n \"using a Global variable set as 'password' is recommended\",\n )\n ]\n\n outputs = [\n Output(display_name=\"env_set\", name=\"env_set\", method=\"process_inputs\"),\n ]\n\n def process_inputs(self) -> Message:\n fake_file = io.StringIO(self.dotenv_file_content)\n result = load_dotenv(stream=fake_file, override=True)\n\n message = Message(text=\"No variables found in .env\")\n if result:\n message = Message(text=\"Loaded .env\")\n return message\n"},"dotenv_file_content":{"_input_type":"MultilineSecretInput","advanced":false,"display_name":"Dotenv file content","dynamic":false,"info":"Paste the content of your .env file directly, since contents are sensitive, using a Global variable set as 'password' is recommended","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"dotenv_file_content","override_skip":false,"password":true,"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":""}},"tool_mode":false},"GetEnvVar":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Gets the value of an environment variable from the system.","display_name":"Get Environment Variable","documentation":"","edited":false,"field_order":["env_var_name"],"frozen":false,"icon":"AstraDB","legacy":true,"metadata":{"code_hash":"083f0a94f380","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.datastax.getenvvar.GetEnvVar"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Environment Variable Value","group_outputs":false,"method":"process_inputs","name":"env_var_value","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 os\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import StrInput\nfrom lfx.schema.message import Message\nfrom lfx.template.field.base import Output\n\n\nclass GetEnvVar(Component):\n display_name = \"Get Environment Variable\"\n description = \"Gets the value of an environment variable from the system.\"\n icon = \"AstraDB\"\n legacy = True\n\n inputs = [\n StrInput(\n name=\"env_var_name\",\n display_name=\"Environment Variable Name\",\n info=\"Name of the environment variable to get\",\n )\n ]\n\n outputs = [\n Output(display_name=\"Environment Variable Value\", name=\"env_var_value\", method=\"process_inputs\"),\n ]\n\n def process_inputs(self) -> Message:\n if self.env_var_name not in os.environ:\n msg = f\"Environment variable {self.env_var_name} not set\"\n raise ValueError(msg)\n return Message(text=os.environ[self.env_var_name])\n"},"env_var_name":{"_input_type":"StrInput","advanced":false,"display_name":"Environment Variable Name","dynamic":false,"info":"Name of the environment variable to get","list":false,"list_add_label":"Add More","load_from_db":false,"name":"env_var_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false},"GraphRAG":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Graph RAG traversal for vector store.","display_name":"Graph RAG","documentation":"","edited":false,"field_order":["embedding_model","vector_store","edge_definition","strategy","search_query","graphrag_strategy_kwargs"],"frozen":false,"icon":"AstraDB","legacy":false,"metadata":{"code_hash":"4d83709a5f5f","dependencies":{"dependencies":[{"name":"graph_retriever","version":"0.8.0"},{"name":"langchain_graph_retriever","version":"0.8.0"},{"name":"lfx","version":null}],"total_dependencies":3},"module":"lfx.components.datastax.graph_rag.GraphRAGComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 inspect\nfrom abc import ABC\n\nimport graph_retriever.strategies as strategies_module\nfrom langchain_graph_retriever import GraphRetriever\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.inputs.inputs import DropdownInput, HandleInput, MultilineInput, NestedDictInput, StrInput\nfrom lfx.schema.data import Data\n\n\ndef traversal_strategies() -> list[str]:\n \"\"\"Retrieves a list of class names from the strategies_module.\n\n This function uses the `inspect` module to get all the class members\n from the `strategies_module` and returns their names as a list of strings.\n\n Returns:\n list[str]: A list of strategy class names.\n \"\"\"\n classes = inspect.getmembers(strategies_module, inspect.isclass)\n return [name for name, cls in classes if ABC not in cls.__bases__]\n\n\nclass GraphRAGComponent(LCVectorStoreComponent):\n \"\"\"GraphRAGComponent is a component for performing Graph RAG traversal in a vector store.\n\n Attributes:\n display_name (str): The display name of the component.\n description (str): A brief description of the component.\n name (str): The name of the component.\n icon (str): The icon representing the component.\n inputs (list): A list of input configurations for the component.\n\n Methods:\n _build_search_args():\n Builds the arguments required for the search operation.\n search_documents() -> list[Data]:\n Searches for documents using the specified strategy, edge definition, and query.\n _edge_definition_from_input() -> tuple:\n Processes the edge definition input and returns it as a tuple.\n \"\"\"\n\n display_name: str = \"Graph RAG\"\n description: str = \"Graph RAG traversal for vector store.\"\n name = \"GraphRAG\"\n icon: str = \"AstraDB\"\n\n inputs = [\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Specify the Embedding Model. Not required for Astra Vectorize collections.\",\n required=False,\n ),\n HandleInput(\n name=\"vector_store\",\n display_name=\"Vector Store Connection\",\n input_types=[\"VectorStore\"],\n info=\"Connection to Vector Store.\",\n ),\n StrInput(\n name=\"edge_definition\",\n display_name=\"Edge Definition\",\n info=\"Edge definition for the graph traversal.\",\n ),\n DropdownInput(\n name=\"strategy\",\n display_name=\"Traversal Strategies\",\n options=traversal_strategies(),\n ),\n MultilineInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n tool_mode=True,\n ),\n NestedDictInput(\n name=\"graphrag_strategy_kwargs\",\n display_name=\"Strategy Parameters\",\n info=(\n \"Optional dictionary of additional parameters for the retrieval strategy. \"\n \"Please see https://datastax.github.io/graph-rag/reference/graph_retriever/strategies/ for details.\"\n ),\n advanced=True,\n ),\n ]\n\n def search_documents(self) -> list[Data]:\n \"\"\"Searches for documents using the graph retriever based on the selected strategy, edge definition, and query.\n\n Returns:\n list[Data]: A list of retrieved documents.\n\n Raises:\n AttributeError: If there is an issue with attribute access.\n TypeError: If there is a type mismatch.\n ValueError: If there is a value error.\n \"\"\"\n additional_params = self.graphrag_strategy_kwargs or {}\n\n # Invoke the graph retriever based on the selected strategy, edge definition, and query\n strategy_class = getattr(strategies_module, self.strategy)\n retriever = GraphRetriever(\n store=self.vector_store,\n edges=[self._evaluate_edge_definition_input()],\n strategy=strategy_class(**additional_params),\n )\n\n return docs_to_data(retriever.invoke(self.search_query))\n\n def _edge_definition_from_input(self) -> tuple:\n \"\"\"Generates the edge definition from the input data.\n\n Returns:\n tuple: A tuple representing the edge definition.\n \"\"\"\n values = self.edge_definition.split(\",\")\n values = [value.strip() for value in values]\n\n return tuple(values)\n\n def _evaluate_edge_definition_input(self) -> tuple:\n from graph_retriever.edges.metadata import Id\n\n \"\"\"Evaluates the edge definition, converting any function calls from strings.\n\n Args:\n edge_definition (tuple): The edge definition to evaluate.\n\n Returns:\n tuple: The evaluated edge definition.\n \"\"\"\n evaluated_values = []\n for value in self._edge_definition_from_input():\n if value == \"Id()\":\n evaluated_values.append(Id()) # Evaluate Id() as a function call\n else:\n evaluated_values.append(value)\n return tuple(evaluated_values)\n"},"edge_definition":{"_input_type":"StrInput","advanced":false,"display_name":"Edge Definition","dynamic":false,"info":"Edge definition for the graph traversal.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"edge_definition","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"embedding_model":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding Model","dynamic":false,"info":"Specify the Embedding Model. Not required for Astra Vectorize collections.","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding_model","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"graphrag_strategy_kwargs":{"_input_type":"NestedDictInput","advanced":true,"display_name":"Strategy Parameters","dynamic":false,"info":"Optional dictionary of additional parameters for the retrieval strategy. Please see https://datastax.github.io/graph-rag/reference/graph_retriever/strategies/ for details.","list":false,"list_add_label":"Add More","name":"graphrag_strategy_kwargs","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":"NestedDict","value":{}},"search_query":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Search Query","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"search_query","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"strategy":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Traversal Strategies","dynamic":false,"external_options":{},"info":"","name":"strategy","options":["Eager","Mmr","NodeTracker","Scored"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"vector_store":{"_input_type":"HandleInput","advanced":false,"display_name":"Vector Store Connection","dynamic":false,"info":"Connection to Vector Store.","input_types":["VectorStore"],"list":false,"list_add_label":"Add More","name":"vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""}},"tool_mode":false},"HCD":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Implementation of Vector Store using Hyper-Converged Database (HCD) with search capabilities","display_name":"Hyper-Converged Database","documentation":"https://docs.langflow.org/bundles-datastax","edited":false,"field_order":["collection_name","username","password","api_endpoint","ingest_data","search_query","should_cache_vector_store","namespace","ca_certificate","metric","batch_size","bulk_insert_batch_concurrency","bulk_insert_overwrite_concurrency","bulk_delete_concurrency","setup_mode","pre_delete_collection","metadata_indexing_include","embedding","metadata_indexing_exclude","collection_indexing_policy","number_of_results","search_type","search_score_threshold","search_filter"],"frozen":false,"icon":"HCD","legacy":false,"metadata":{"code_hash":"25f009b9e171","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_astradb","version":"0.6.1"},{"name":"astrapy","version":"2.1.0"}],"total_dependencies":3},"module":"lfx.components.datastax.hcd.HCDVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_endpoint":{"_input_type":"SecretStrInput","advanced":false,"display_name":"HCD API Endpoint","dynamic":false,"info":"API endpoint URL for the HCD service.","input_types":[],"load_from_db":true,"name":"api_endpoint","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"HCD_API_ENDPOINT"},"batch_size":{"_input_type":"IntInput","advanced":true,"display_name":"Batch Size","dynamic":false,"info":"Optional number of data to process in a single batch.","list":false,"list_add_label":"Add More","name":"batch_size","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":""},"bulk_delete_concurrency":{"_input_type":"IntInput","advanced":true,"display_name":"Bulk Delete Concurrency","dynamic":false,"info":"Optional concurrency level for bulk delete operations.","list":false,"list_add_label":"Add More","name":"bulk_delete_concurrency","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":""},"bulk_insert_batch_concurrency":{"_input_type":"IntInput","advanced":true,"display_name":"Bulk Insert Batch Concurrency","dynamic":false,"info":"Optional concurrency level for bulk insert operations.","list":false,"list_add_label":"Add More","name":"bulk_insert_batch_concurrency","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":""},"bulk_insert_overwrite_concurrency":{"_input_type":"IntInput","advanced":true,"display_name":"Bulk Insert Overwrite Concurrency","dynamic":false,"info":"Optional concurrency level for bulk insert operations that overwrite existing data.","list":false,"list_add_label":"Add More","name":"bulk_insert_overwrite_concurrency","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":""},"ca_certificate":{"_input_type":"MultilineInput","advanced":true,"ai_enabled":false,"copy_field":false,"display_name":"CA Certificate","dynamic":false,"info":"Optional CA certificate for TLS connections to HCD.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"ca_certificate","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":""},"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":"from lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.inputs.inputs import DictInput, FloatInput\nfrom lfx.io import (\n BoolInput,\n DropdownInput,\n HandleInput,\n IntInput,\n MultilineInput,\n SecretStrInput,\n StrInput,\n)\nfrom lfx.schema.data import Data\n\n\nclass HCDVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Hyper-Converged Database\"\n description: str = \"Implementation of Vector Store using Hyper-Converged Database (HCD) with search capabilities\"\n name = \"HCD\"\n documentation: str = \"https://docs.langflow.org/bundles-datastax\"\n icon: str = \"HCD\"\n\n inputs = [\n StrInput(\n name=\"collection_name\",\n display_name=\"Collection Name\",\n info=\"The name of the collection within HCD where the vectors will be stored.\",\n required=True,\n ),\n StrInput(\n name=\"username\",\n display_name=\"HCD Username\",\n info=\"Authentication username for accessing HCD.\",\n value=\"hcd-superuser\",\n required=True,\n ),\n SecretStrInput(\n name=\"password\",\n display_name=\"HCD Password\",\n info=\"Authentication password for accessing HCD.\",\n value=\"HCD_PASSWORD\",\n required=True,\n ),\n SecretStrInput(\n name=\"api_endpoint\",\n display_name=\"HCD API Endpoint\",\n info=\"API endpoint URL for the HCD service.\",\n value=\"HCD_API_ENDPOINT\",\n required=True,\n ),\n *LCVectorStoreComponent.inputs,\n StrInput(\n name=\"namespace\",\n display_name=\"Namespace\",\n info=\"Optional namespace within HCD to use for the collection.\",\n value=\"default_namespace\",\n advanced=True,\n ),\n MultilineInput(\n name=\"ca_certificate\",\n display_name=\"CA Certificate\",\n info=\"Optional CA certificate for TLS connections to HCD.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"metric\",\n display_name=\"Metric\",\n info=\"Optional distance metric for vector comparisons in the vector store.\",\n options=[\"cosine\", \"dot_product\", \"euclidean\"],\n advanced=True,\n ),\n IntInput(\n name=\"batch_size\",\n display_name=\"Batch Size\",\n info=\"Optional number of data to process in a single batch.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_batch_concurrency\",\n display_name=\"Bulk Insert Batch Concurrency\",\n info=\"Optional concurrency level for bulk insert operations.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_overwrite_concurrency\",\n display_name=\"Bulk Insert Overwrite Concurrency\",\n info=\"Optional concurrency level for bulk insert operations that overwrite existing data.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_delete_concurrency\",\n display_name=\"Bulk Delete Concurrency\",\n info=\"Optional concurrency level for bulk delete operations.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"setup_mode\",\n display_name=\"Setup Mode\",\n info=\"Configuration mode for setting up the vector store, with options like 'Sync', 'Async', or 'Off'.\",\n options=[\"Sync\", \"Async\", \"Off\"],\n advanced=True,\n value=\"Sync\",\n ),\n BoolInput(\n name=\"pre_delete_collection\",\n display_name=\"Pre Delete Collection\",\n info=\"Boolean flag to determine whether to delete the collection before creating a new one.\",\n advanced=True,\n ),\n StrInput(\n name=\"metadata_indexing_include\",\n display_name=\"Metadata Indexing Include\",\n info=\"Optional list of metadata fields to include in the indexing.\",\n advanced=True,\n ),\n HandleInput(\n name=\"embedding\",\n display_name=\"Embedding or Astra Vectorize\",\n input_types=[\"Embeddings\", \"dict\"],\n # TODO: This should be optional, but need to refactor langchain-astradb first.\n info=\"Allows either an embedding model or an Astra Vectorize configuration.\",\n ),\n StrInput(\n name=\"metadata_indexing_exclude\",\n display_name=\"Metadata Indexing Exclude\",\n info=\"Optional list of metadata fields to exclude from the indexing.\",\n advanced=True,\n ),\n StrInput(\n name=\"collection_indexing_policy\",\n display_name=\"Collection Indexing Policy\",\n info=\"Optional dictionary defining the indexing policy for the collection.\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n is_list=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBVectorStore\n from langchain_astradb.utils.astradb import SetupMode\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n try:\n from astrapy.authentication import UsernamePasswordTokenProvider\n from astrapy.constants import Environment\n except ImportError as e:\n msg = \"Could not import astrapy integration package. Please install it with `pip install astrapy`.\"\n raise ImportError(msg) from e\n\n try:\n if not self.setup_mode:\n self.setup_mode = self._inputs[\"setup_mode\"].options[0]\n\n setup_mode_value = SetupMode[self.setup_mode.upper()]\n except KeyError as e:\n msg = f\"Invalid setup mode: {self.setup_mode}\"\n raise ValueError(msg) from e\n\n if not isinstance(self.embedding, dict):\n embedding_dict = {\"embedding\": self.embedding}\n else:\n from astrapy.info import VectorServiceOptions\n\n dict_options = self.embedding.get(\"collection_vector_service_options\", {})\n dict_options[\"authentication\"] = {\n k: v for k, v in dict_options.get(\"authentication\", {}).items() if k and v\n }\n dict_options[\"parameters\"] = {k: v for k, v in dict_options.get(\"parameters\", {}).items() if k and v}\n embedding_dict = {\"collection_vector_service_options\": VectorServiceOptions.from_dict(dict_options)}\n collection_embedding_api_key = self.embedding.get(\"collection_embedding_api_key\")\n if collection_embedding_api_key:\n embedding_dict[\"collection_embedding_api_key\"] = collection_embedding_api_key\n\n token_provider = UsernamePasswordTokenProvider(self.username, self.password)\n vector_store_kwargs = {\n **embedding_dict,\n \"collection_name\": self.collection_name,\n \"token\": token_provider,\n \"api_endpoint\": self.api_endpoint,\n \"namespace\": self.namespace,\n \"metric\": self.metric or None,\n \"batch_size\": self.batch_size or None,\n \"bulk_insert_batch_concurrency\": self.bulk_insert_batch_concurrency or None,\n \"bulk_insert_overwrite_concurrency\": self.bulk_insert_overwrite_concurrency or None,\n \"bulk_delete_concurrency\": self.bulk_delete_concurrency or None,\n \"setup_mode\": setup_mode_value,\n \"pre_delete_collection\": self.pre_delete_collection or False,\n \"environment\": Environment.HCD,\n }\n\n if self.metadata_indexing_include:\n vector_store_kwargs[\"metadata_indexing_include\"] = self.metadata_indexing_include\n elif self.metadata_indexing_exclude:\n vector_store_kwargs[\"metadata_indexing_exclude\"] = self.metadata_indexing_exclude\n elif self.collection_indexing_policy:\n vector_store_kwargs[\"collection_indexing_policy\"] = self.collection_indexing_policy\n\n try:\n vector_store = AstraDBVectorStore(**vector_store_kwargs)\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n return args\n\n def search_documents(self) -> list[Data]:\n vector_store = self.build_vector_store()\n\n self.log(f\"Search query: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n except Exception as e:\n msg = f\"Error performing search in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n return data\n self.log(\"No search input provided. Skipping search.\")\n return []\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"},"collection_indexing_policy":{"_input_type":"StrInput","advanced":true,"display_name":"Collection Indexing Policy","dynamic":false,"info":"Optional dictionary defining the indexing policy for the collection.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"collection_indexing_policy","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"collection_name":{"_input_type":"StrInput","advanced":false,"display_name":"Collection Name","dynamic":false,"info":"The name of the collection within HCD where the vectors will be stored.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"collection_name","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding or Astra Vectorize","dynamic":false,"info":"Allows either an embedding model or an Astra Vectorize configuration.","input_types":["Embeddings","dict"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"metadata_indexing_exclude":{"_input_type":"StrInput","advanced":true,"display_name":"Metadata Indexing Exclude","dynamic":false,"info":"Optional list of metadata fields to exclude from the indexing.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"metadata_indexing_exclude","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"metadata_indexing_include":{"_input_type":"StrInput","advanced":true,"display_name":"Metadata Indexing Include","dynamic":false,"info":"Optional list of metadata fields to include in the indexing.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"metadata_indexing_include","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"metric":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Metric","dynamic":false,"external_options":{},"info":"Optional distance metric for vector comparisons in the vector store.","name":"metric","options":["cosine","dot_product","euclidean"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":""},"namespace":{"_input_type":"StrInput","advanced":true,"display_name":"Namespace","dynamic":false,"info":"Optional namespace within HCD to use for the collection.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"namespace","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"default_namespace"},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"HCD Password","dynamic":false,"info":"Authentication password for accessing HCD.","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","required":true,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"HCD_PASSWORD"},"pre_delete_collection":{"_input_type":"BoolInput","advanced":true,"display_name":"Pre Delete Collection","dynamic":false,"info":"Boolean flag to determine whether to delete the collection before creating a new one.","list":false,"list_add_label":"Add More","name":"pre_delete_collection","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"search_filter":{"_input_type":"DictInput","advanced":true,"display_name":"Search Metadata Filter","dynamic":false,"info":"Optional dictionary of filters to apply to the search query.","list":true,"list_add_label":"Add More","name":"search_filter","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"search_score_threshold":{"_input_type":"FloatInput","advanced":true,"display_name":"Search Score Threshold","dynamic":false,"info":"Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')","list":false,"list_add_label":"Add More","name":"search_score_threshold","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.0},"search_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Type","dynamic":false,"external_options":{},"info":"Search type to use","name":"search_type","options":["Similarity","Similarity with score threshold","MMR (Max Marginal Relevance)"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Similarity"},"setup_mode":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Setup Mode","dynamic":false,"external_options":{},"info":"Configuration mode for setting up the vector store, with options like 'Sync', 'Async', or 'Off'.","name":"setup_mode","options":["Sync","Async","Off"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Sync"},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"username":{"_input_type":"StrInput","advanced":false,"display_name":"HCD Username","dynamic":false,"info":"Authentication username for accessing HCD.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"hcd-superuser"}},"tool_mode":false}}],["deepseek",{"DeepSeekModelComponent":{"base_classes":["LanguageModel","Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate text using DeepSeek LLMs.","display_name":"DeepSeek","documentation":"","edited":false,"field_order":["input_value","system_message","stream","max_tokens","model_kwargs","json_mode","model_name","api_base","api_key","temperature","seed"],"frozen":false,"icon":"DeepSeek","legacy":false,"metadata":{"code_hash":"c8dac7a258d7","dependencies":{"dependencies":[{"name":"requests","version":"2.32.5"},{"name":"pydantic","version":"2.11.10"},{"name":"typing_extensions","version":"4.15.0"},{"name":"lfx","version":null},{"name":"langchain_openai","version":"0.3.23"},{"name":"openai","version":"1.82.1"}],"total_dependencies":6},"keywords":["model","llm","language model","large language model"],"module":"lfx.components.deepseek.deepseek.DeepSeekModelComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Model Response","group_outputs":false,"method":"text_response","name":"text_output","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Language Model","group_outputs":false,"method":"build_model","name":"model_output","selected":"LanguageModel","tool_mode":true,"types":["LanguageModel"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_base":{"_input_type":"StrInput","advanced":true,"display_name":"DeepSeek API Base","dynamic":false,"info":"Base URL for API requests. Defaults to https://api.deepseek.com","list":false,"list_add_label":"Add More","load_from_db":false,"name":"api_base","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"https://api.deepseek.com"},"api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"DeepSeek API Key","dynamic":false,"info":"The DeepSeek 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 requests\nfrom pydantic.v1 import SecretStr\nfrom typing_extensions import override\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.inputs.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\nDEEPSEEK_MODELS = [\"deepseek-chat\"]\n\n\nclass DeepSeekModelComponent(LCModelComponent):\n display_name = \"DeepSeek\"\n description = \"Generate text using DeepSeek LLMs.\"\n icon = \"DeepSeek\"\n\n inputs = [\n *LCModelComponent.get_base_inputs(),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"Maximum number of tokens to generate. Set to 0 for unlimited.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n info=\"DeepSeek model to use\",\n options=DEEPSEEK_MODELS,\n value=\"deepseek-chat\",\n refresh_button=True,\n ),\n StrInput(\n name=\"api_base\",\n display_name=\"DeepSeek API Base\",\n advanced=True,\n info=\"Base URL for API requests. Defaults to https://api.deepseek.com\",\n value=\"https://api.deepseek.com\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"DeepSeek API Key\",\n info=\"The DeepSeek API Key\",\n advanced=False,\n required=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n info=\"Controls randomness in responses\",\n value=1.0,\n range_spec=RangeSpec(min=0, max=2, step=0.01),\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def get_models(self) -> list[str]:\n if not self.api_key:\n return DEEPSEEK_MODELS\n\n url = f\"{self.api_base}/models\"\n headers = {\"Authorization\": f\"Bearer {self.api_key}\", \"Accept\": \"application/json\"}\n\n try:\n response = requests.get(url, headers=headers, timeout=10)\n response.raise_for_status()\n model_list = response.json()\n return [model[\"id\"] for model in model_list.get(\"data\", [])]\n except requests.RequestException as e:\n self.status = f\"Error fetching models: {e}\"\n return DEEPSEEK_MODELS\n\n @override\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n if field_name in {\"api_key\", \"api_base\", \"model_name\"}:\n models = self.get_models()\n build_config[\"model_name\"][\"options\"] = models\n return build_config\n\n def build_model(self) -> LanguageModel:\n try:\n from langchain_openai import ChatOpenAI\n except ImportError as e:\n msg = \"langchain-openai not installed. Please install with `pip install langchain-openai`\"\n raise ImportError(msg) from e\n\n api_key = SecretStr(self.api_key).get_secret_value() if self.api_key else None\n output = ChatOpenAI(\n model=self.model_name,\n temperature=self.temperature if self.temperature is not None else 0.1,\n max_tokens=self.max_tokens or None,\n model_kwargs=self.model_kwargs or {},\n base_url=self.api_base,\n api_key=api_key,\n streaming=self.stream if hasattr(self, \"stream\") else False,\n seed=self.seed,\n )\n\n if self.json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get message from DeepSeek API exception.\"\"\"\n try:\n from openai import BadRequestError\n\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n except ImportError:\n pass\n return None\n"},"input_value":{"_input_type":"MessageInput","advanced":false,"display_name":"Input","dynamic":false,"info":"","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","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":""},"json_mode":{"_input_type":"BoolInput","advanced":true,"display_name":"JSON Mode","dynamic":false,"info":"If True, it will output JSON regardless of passing a schema.","list":false,"list_add_label":"Add More","name":"json_mode","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Max Tokens","dynamic":false,"info":"Maximum number of tokens to generate. Set to 0 for unlimited.","list":false,"list_add_label":"Add More","name":"max_tokens","override_skip":false,"placeholder":"","range_spec":{"max":128000.0,"min":0.0,"step":0.1,"step_type":"float"},"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"int","value":""},"model_kwargs":{"_input_type":"DictInput","advanced":true,"display_name":"Model Kwargs","dynamic":false,"info":"Additional keyword arguments to pass to the model.","list":false,"list_add_label":"Add More","name":"model_kwargs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"track_in_telemetry":false,"type":"dict","value":{}},"model_name":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Model Name","dynamic":false,"external_options":{},"info":"DeepSeek model to use","name":"model_name","options":["deepseek-chat"],"options_metadata":[],"override_skip":false,"placeholder":"","refresh_button":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"deepseek-chat"},"seed":{"_input_type":"IntInput","advanced":true,"display_name":"Seed","dynamic":false,"info":"The seed controls the reproducibility of the job.","list":false,"list_add_label":"Add More","name":"seed","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":1},"stream":{"_input_type":"BoolInput","advanced":true,"display_name":"Stream","dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","list":false,"list_add_label":"Add More","name":"stream","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"system_message":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"System Message","dynamic":false,"info":"System message to pass to the model.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"system_message","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":""},"temperature":{"_input_type":"SliderInput","advanced":true,"display_name":"Temperature","dynamic":false,"info":"Controls randomness in responses","max_label":"","max_label_icon":"","min_label":"","min_label_icon":"","name":"temperature","override_skip":false,"placeholder":"","range_spec":{"max":2.0,"min":0.0,"step":0.01,"step_type":"float"},"required":false,"show":true,"slider_buttons":false,"slider_buttons_options":[],"slider_input":false,"title_case":false,"tool_mode":false,"track_in_telemetry":false,"type":"slider","value":1.0}},"tool_mode":false}}],["docling",{"ChunkDoclingDocument":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Use the DocumentDocument chunkers to split the document into chunks.","display_name":"Chunk DoclingDocument","documentation":"https://docling-project.github.io/docling/concepts/chunking/","edited":false,"field_order":["data_inputs","chunker","provider","hf_model_name","openai_model_name","max_tokens","doc_key"],"frozen":false,"icon":"Docling","legacy":false,"metadata":{"code_hash":"d84ce7ffc6cb","dependencies":{"dependencies":[{"name":"tiktoken","version":"0.12.0"},{"name":"docling_core","version":"2.54.0"},{"name":"lfx","version":null}],"total_dependencies":3},"module":"lfx.components.docling.chunk_docling_document.ChunkDoclingDocumentComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"chunk_documents","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","chunker":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Chunker","dynamic":false,"external_options":{},"info":"Which chunker to use.","name":"chunker","options":["HybridChunker","HierarchicalChunker"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"HybridChunker"},"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\n\nimport tiktoken\nfrom docling_core.transforms.chunker import BaseChunker, DocMeta\nfrom docling_core.transforms.chunker.hierarchical_chunker import HierarchicalChunker\n\nfrom lfx.base.data.docling_utils import extract_docling_documents\nfrom lfx.custom import Component\nfrom lfx.io import DropdownInput, HandleInput, IntInput, MessageTextInput, Output, StrInput\nfrom lfx.schema import Data, DataFrame\n\n\nclass ChunkDoclingDocumentComponent(Component):\n display_name: str = \"Chunk DoclingDocument\"\n description: str = \"Use the DocumentDocument chunkers to split the document into chunks.\"\n documentation = \"https://docling-project.github.io/docling/concepts/chunking/\"\n icon = \"Docling\"\n name = \"ChunkDoclingDocument\"\n\n inputs = [\n HandleInput(\n name=\"data_inputs\",\n display_name=\"Data or DataFrame\",\n info=\"The data with documents to split in chunks.\",\n input_types=[\"Data\", \"DataFrame\"],\n required=True,\n ),\n DropdownInput(\n name=\"chunker\",\n display_name=\"Chunker\",\n options=[\"HybridChunker\", \"HierarchicalChunker\"],\n info=(\"Which chunker to use.\"),\n value=\"HybridChunker\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"provider\",\n display_name=\"Provider\",\n options=[\"Hugging Face\", \"OpenAI\"],\n info=(\"Which tokenizer provider.\"),\n value=\"Hugging Face\",\n show=True,\n real_time_refresh=True,\n advanced=True,\n dynamic=True,\n ),\n StrInput(\n name=\"hf_model_name\",\n display_name=\"HF model name\",\n info=(\n \"Model name of the tokenizer to use with the HybridChunker when Hugging Face is chosen as a tokenizer.\"\n ),\n value=\"sentence-transformers/all-MiniLM-L6-v2\",\n show=True,\n advanced=True,\n dynamic=True,\n ),\n StrInput(\n name=\"openai_model_name\",\n display_name=\"OpenAI model name\",\n info=(\"Model name of the tokenizer to use with the HybridChunker when OpenAI is chosen as a tokenizer.\"),\n value=\"gpt-4o\",\n show=False,\n advanced=True,\n dynamic=True,\n ),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Maximum tokens\",\n info=(\"Maximum number of tokens for the HybridChunker.\"),\n show=True,\n required=False,\n advanced=True,\n dynamic=True,\n ),\n MessageTextInput(\n name=\"doc_key\",\n display_name=\"Doc Key\",\n info=\"The key to use for the DoclingDocument column.\",\n value=\"doc\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"chunk_documents\"),\n ]\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n if field_name == \"chunker\":\n provider_type = build_config[\"provider\"][\"value\"]\n is_hf = provider_type == \"Hugging Face\"\n is_openai = provider_type == \"OpenAI\"\n if field_value == \"HybridChunker\":\n build_config[\"provider\"][\"show\"] = True\n build_config[\"hf_model_name\"][\"show\"] = is_hf\n build_config[\"openai_model_name\"][\"show\"] = is_openai\n build_config[\"max_tokens\"][\"show\"] = True\n else:\n build_config[\"provider\"][\"show\"] = False\n build_config[\"hf_model_name\"][\"show\"] = False\n build_config[\"openai_model_name\"][\"show\"] = False\n build_config[\"max_tokens\"][\"show\"] = False\n elif field_name == \"provider\" and build_config[\"chunker\"][\"value\"] == \"HybridChunker\":\n if field_value == \"Hugging Face\":\n build_config[\"hf_model_name\"][\"show\"] = True\n build_config[\"openai_model_name\"][\"show\"] = False\n elif field_value == \"OpenAI\":\n build_config[\"hf_model_name\"][\"show\"] = False\n build_config[\"openai_model_name\"][\"show\"] = True\n\n return build_config\n\n def _docs_to_data(self, docs) -> list[Data]:\n return [Data(text=doc.page_content, data=doc.metadata) for doc in docs]\n\n def chunk_documents(self) -> DataFrame:\n documents, warning = extract_docling_documents(self.data_inputs, self.doc_key)\n if warning:\n self.status = warning\n\n chunker: BaseChunker\n if self.chunker == \"HybridChunker\":\n try:\n from docling_core.transforms.chunker.hybrid_chunker import HybridChunker\n except ImportError as e:\n msg = (\n \"HybridChunker is not installed. Please install it with `uv pip install docling-core[chunking] \"\n \"or `uv pip install transformers`\"\n )\n raise ImportError(msg) from e\n max_tokens: int | None = self.max_tokens if self.max_tokens else None\n if self.provider == \"Hugging Face\":\n try:\n from docling_core.transforms.chunker.tokenizer.huggingface import HuggingFaceTokenizer\n except ImportError as e:\n msg = (\n \"HuggingFaceTokenizer is not installed.\"\n \" Please install it with `uv pip install docling-core[chunking]`\"\n )\n raise ImportError(msg) from e\n tokenizer = HuggingFaceTokenizer.from_pretrained(\n model_name=self.hf_model_name,\n max_tokens=max_tokens,\n )\n elif self.provider == \"OpenAI\":\n try:\n from docling_core.transforms.chunker.tokenizer.openai import OpenAITokenizer\n except ImportError as e:\n msg = (\n \"OpenAITokenizer is not installed.\"\n \" Please install it with `uv pip install docling-core[chunking]`\"\n \" or `uv pip install transformers`\"\n )\n raise ImportError(msg) from e\n if max_tokens is None:\n max_tokens = 128 * 1024 # context window length required for OpenAI tokenizers\n tokenizer = OpenAITokenizer(\n tokenizer=tiktoken.encoding_for_model(self.openai_model_name), max_tokens=max_tokens\n )\n chunker = HybridChunker(\n tokenizer=tokenizer,\n )\n elif self.chunker == \"HierarchicalChunker\":\n chunker = HierarchicalChunker()\n\n results: list[Data] = []\n try:\n for doc in documents:\n for chunk in chunker.chunk(dl_doc=doc):\n enriched_text = chunker.contextualize(chunk=chunk)\n meta = DocMeta.model_validate(chunk.meta)\n\n results.append(\n Data(\n data={\n \"text\": enriched_text,\n \"document_id\": f\"{doc.origin.binary_hash}\",\n \"doc_items\": json.dumps([item.self_ref for item in meta.doc_items]),\n }\n )\n )\n\n except Exception as e:\n msg = f\"Error splitting text: {e}\"\n raise TypeError(msg) from e\n\n return DataFrame(results)\n"},"data_inputs":{"_input_type":"HandleInput","advanced":false,"display_name":"Data or DataFrame","dynamic":false,"info":"The data with documents to split in chunks.","input_types":["Data","DataFrame"],"list":false,"list_add_label":"Add More","name":"data_inputs","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"doc_key":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Doc Key","dynamic":false,"info":"The key to use for the DoclingDocument column.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"doc_key","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":"doc"},"hf_model_name":{"_input_type":"StrInput","advanced":true,"display_name":"HF model name","dynamic":true,"info":"Model name of the tokenizer to use with the HybridChunker when Hugging Face is chosen as a tokenizer.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"hf_model_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"sentence-transformers/all-MiniLM-L6-v2"},"max_tokens":{"_input_type":"IntInput","advanced":true,"display_name":"Maximum tokens","dynamic":true,"info":"Maximum number of tokens for the HybridChunker.","list":false,"list_add_label":"Add More","name":"max_tokens","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":""},"openai_model_name":{"_input_type":"StrInput","advanced":true,"display_name":"OpenAI model name","dynamic":true,"info":"Model name of the tokenizer to use with the HybridChunker when OpenAI is chosen as a tokenizer.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"openai_model_name","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"gpt-4o"},"provider":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Provider","dynamic":true,"external_options":{},"info":"Which tokenizer provider.","name":"provider","options":["Hugging Face","OpenAI"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Hugging Face"}},"tool_mode":false},"DoclingInline":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Uses Docling to process input documents running the Docling models locally.","display_name":"Docling","documentation":"https://docling-project.github.io/docling/","edited":false,"field_order":["path","file_path","separator","silent_errors","delete_server_file_after_processing","ignore_unsupported_extensions","ignore_unspecified_files","pipeline","ocr_engine","do_picture_classification","pic_desc_llm","pic_desc_prompt"],"frozen":false,"icon":"Docling","legacy":false,"metadata":{"code_hash":"d76b3853ceb4","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"docling","version":"2.63.0"}],"total_dependencies":2},"module":"lfx.components.docling.docling_inline.DoclingInlineComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Files","group_outputs":false,"method":"load_files","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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 time\nfrom multiprocessing import Queue, get_context\nfrom queue import Empty\n\nfrom lfx.base.data import BaseFileComponent\nfrom lfx.base.data.docling_utils import _serialize_pydantic_model, docling_worker\nfrom lfx.inputs import BoolInput, DropdownInput, HandleInput, StrInput\nfrom lfx.schema import Data\n\n\nclass DoclingInlineComponent(BaseFileComponent):\n display_name = \"Docling\"\n description = \"Uses Docling to process input documents running the Docling models locally.\"\n documentation = \"https://docling-project.github.io/docling/\"\n trace_type = \"tool\"\n icon = \"Docling\"\n name = \"DoclingInline\"\n\n # https://docling-project.github.io/docling/usage/supported_formats/\n VALID_EXTENSIONS = [\n \"adoc\",\n \"asciidoc\",\n \"asc\",\n \"bmp\",\n \"csv\",\n \"dotx\",\n \"dotm\",\n \"docm\",\n \"docx\",\n \"htm\",\n \"html\",\n \"jpeg\",\n \"json\",\n \"md\",\n \"pdf\",\n \"png\",\n \"potx\",\n \"ppsx\",\n \"pptm\",\n \"potm\",\n \"ppsm\",\n \"pptx\",\n \"tiff\",\n \"txt\",\n \"xls\",\n \"xlsx\",\n \"xhtml\",\n \"xml\",\n \"webp\",\n ]\n\n inputs = [\n *BaseFileComponent.get_base_inputs(),\n DropdownInput(\n name=\"pipeline\",\n display_name=\"Pipeline\",\n info=\"Docling pipeline to use\",\n options=[\"standard\", \"vlm\"],\n value=\"standard\",\n ),\n DropdownInput(\n name=\"ocr_engine\",\n display_name=\"OCR Engine\",\n info=\"OCR engine to use. None will disable OCR.\",\n options=[\"None\", \"easyocr\", \"tesserocr\", \"rapidocr\", \"ocrmac\"],\n value=\"None\",\n ),\n BoolInput(\n name=\"do_picture_classification\",\n display_name=\"Picture classification\",\n info=\"If enabled, the Docling pipeline will classify the pictures type.\",\n value=False,\n ),\n HandleInput(\n name=\"pic_desc_llm\",\n display_name=\"Picture description LLM\",\n info=\"If connected, the model to use for running the picture description task.\",\n input_types=[\"LanguageModel\"],\n required=False,\n ),\n StrInput(\n name=\"pic_desc_prompt\",\n display_name=\"Picture description prompt\",\n value=\"Describe the image in three sentences. Be concise and accurate.\",\n info=\"The user prompt to use when invoking the model.\",\n advanced=True,\n ),\n # TODO: expose more Docling options\n ]\n\n outputs = [\n *BaseFileComponent.get_base_outputs(),\n ]\n\n def _wait_for_result_with_process_monitoring(self, queue: Queue, proc, timeout: int = 300):\n \"\"\"Wait for result from queue while monitoring process health.\n\n Handles cases where process crashes without sending result.\n \"\"\"\n start_time = time.time()\n\n while time.time() - start_time < timeout:\n # Check if process is still alive\n if not proc.is_alive():\n # Process died, try to get any result it might have sent\n try:\n result = queue.get_nowait()\n except Empty:\n # Process died without sending result\n msg = f\"Worker process crashed unexpectedly without producing result. Exit code: {proc.exitcode}\"\n raise RuntimeError(msg) from None\n else:\n self.log(\"Process completed and result retrieved\")\n return result\n\n # Poll the queue instead of blocking\n try:\n result = queue.get(timeout=1)\n except Empty:\n # No result yet, continue monitoring\n continue\n else:\n self.log(\"Result received from worker process\")\n return result\n\n # Overall timeout reached\n msg = f\"Process timed out after {timeout} seconds\"\n raise TimeoutError(msg)\n\n def _terminate_process_gracefully(self, proc, timeout_terminate: int = 10, timeout_kill: int = 5):\n \"\"\"Terminate process gracefully with escalating signals.\n\n First tries SIGTERM, then SIGKILL if needed.\n \"\"\"\n if not proc.is_alive():\n return\n\n self.log(\"Attempting graceful process termination with SIGTERM\")\n proc.terminate() # Send SIGTERM\n proc.join(timeout=timeout_terminate)\n\n if proc.is_alive():\n self.log(\"Process didn't respond to SIGTERM, using SIGKILL\")\n proc.kill() # Send SIGKILL\n proc.join(timeout=timeout_kill)\n\n if proc.is_alive():\n self.log(\"Warning: Process still alive after SIGKILL\")\n\n def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[BaseFileComponent.BaseFile]:\n try:\n from docling.document_converter import DocumentConverter # noqa: F401\n except ImportError as e:\n msg = (\n \"Docling is an optional dependency. Install with `uv pip install 'langflow[docling]'` or refer to the \"\n \"documentation on how to install optional dependencies.\"\n )\n raise ImportError(msg) from e\n\n file_paths = [file.path for file in file_list if file.path]\n\n if not file_paths:\n self.log(\"No files to process.\")\n return file_list\n\n pic_desc_config: dict | None = None\n if self.pic_desc_llm is not None:\n pic_desc_config = _serialize_pydantic_model(self.pic_desc_llm)\n\n ctx = get_context(\"spawn\")\n queue: Queue = ctx.Queue()\n proc = ctx.Process(\n target=docling_worker,\n kwargs={\n \"file_paths\": file_paths,\n \"queue\": queue,\n \"pipeline\": self.pipeline,\n \"ocr_engine\": self.ocr_engine,\n \"do_picture_classification\": self.do_picture_classification,\n \"pic_desc_config\": pic_desc_config,\n \"pic_desc_prompt\": self.pic_desc_prompt,\n },\n )\n\n result = None\n proc.start()\n\n try:\n result = self._wait_for_result_with_process_monitoring(queue, proc, timeout=300)\n except KeyboardInterrupt:\n self.log(\"Docling process cancelled by user\")\n result = []\n except Exception as e:\n self.log(f\"Error during processing: {e}\")\n raise\n finally:\n # Improved cleanup with graceful termination\n try:\n self._terminate_process_gracefully(proc)\n finally:\n # Always close and cleanup queue resources\n try:\n queue.close()\n queue.join_thread()\n except Exception as e: # noqa: BLE001\n # Ignore cleanup errors, but log them\n self.log(f\"Warning: Error during queue cleanup - {e}\")\n\n # Enhanced error checking with dependency-specific handling\n if isinstance(result, dict) and \"error\" in result:\n error_msg = result[\"error\"]\n\n # Handle dependency errors specifically\n if result.get(\"error_type\") == \"dependency_error\":\n dependency_name = result.get(\"dependency_name\", \"Unknown dependency\")\n install_command = result.get(\"install_command\", \"Please check documentation\")\n\n # Create a user-friendly error message\n user_message = (\n f\"Missing OCR dependency: {dependency_name}. \"\n f\"{install_command} \"\n f\"Alternatively, you can set OCR Engine to 'None' to disable OCR processing.\"\n )\n raise ImportError(user_message)\n\n # Handle other specific errors\n if error_msg.startswith(\"Docling is not installed\"):\n raise ImportError(error_msg)\n\n # Handle graceful shutdown\n if \"Worker interrupted by SIGINT\" in error_msg or \"shutdown\" in result:\n self.log(\"Docling process cancelled by user\")\n result = []\n else:\n raise RuntimeError(error_msg)\n\n processed_data = [Data(data={\"doc\": r[\"document\"], \"file_path\": r[\"file_path\"]}) if r else None for r in result]\n return self.rollup_data(file_list, processed_data)\n"},"delete_server_file_after_processing":{"_input_type":"BoolInput","advanced":true,"display_name":"Delete Server File After Processing","dynamic":false,"info":"If true, the Server File Path will be deleted after processing.","list":false,"list_add_label":"Add More","name":"delete_server_file_after_processing","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"do_picture_classification":{"_input_type":"BoolInput","advanced":false,"display_name":"Picture classification","dynamic":false,"info":"If enabled, the Docling pipeline will classify the pictures type.","list":false,"list_add_label":"Add More","name":"do_picture_classification","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"file_path":{"_input_type":"HandleInput","advanced":true,"display_name":"Server File Path","dynamic":false,"info":"Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.","input_types":["Data","Message"],"list":true,"list_add_label":"Add More","name":"file_path","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"ignore_unspecified_files":{"_input_type":"BoolInput","advanced":true,"display_name":"Ignore Unspecified Files","dynamic":false,"info":"If true, Data with no 'file_path' property will be ignored.","list":false,"list_add_label":"Add More","name":"ignore_unspecified_files","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"ignore_unsupported_extensions":{"_input_type":"BoolInput","advanced":true,"display_name":"Ignore Unsupported Extensions","dynamic":false,"info":"If true, files with unsupported extensions will not be processed.","list":false,"list_add_label":"Add More","name":"ignore_unsupported_extensions","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"ocr_engine":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"OCR Engine","dynamic":false,"external_options":{},"info":"OCR engine to use. None will disable OCR.","name":"ocr_engine","options":["None","easyocr","tesserocr","rapidocr","ocrmac"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"None"},"path":{"_input_type":"FileInput","advanced":false,"display_name":"Files","dynamic":false,"fileTypes":["adoc","asciidoc","asc","bmp","csv","dotx","dotm","docm","docx","htm","html","jpeg","json","md","pdf","png","potx","ppsx","pptm","potm","ppsm","pptx","tiff","txt","xls","xlsx","xhtml","xml","webp","zip","tar","tgz","bz2","gz"],"file_path":"","info":"Supported file extensions: adoc, asciidoc, asc, bmp, csv, dotx, dotm, docm, docx, htm, html, jpeg, json, md, pdf, png, potx, ppsx, pptm, potm, ppsm, pptx, tiff, txt, xls, xlsx, xhtml, xml, webp; optionally bundled in file extensions: zip, tar, tgz, bz2, gz","list":true,"list_add_label":"Add More","name":"path","override_skip":false,"placeholder":"","required":false,"show":true,"temp_file":false,"title_case":false,"tool_mode":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"file","value":[]},"pic_desc_llm":{"_input_type":"HandleInput","advanced":false,"display_name":"Picture description LLM","dynamic":false,"info":"If connected, the model to use for running the picture description task.","input_types":["LanguageModel"],"list":false,"list_add_label":"Add More","name":"pic_desc_llm","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"pic_desc_prompt":{"_input_type":"StrInput","advanced":true,"display_name":"Picture description prompt","dynamic":false,"info":"The user prompt to use when invoking the model.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"pic_desc_prompt","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"Describe the image in three sentences. Be concise and accurate."},"pipeline":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Pipeline","dynamic":false,"external_options":{},"info":"Docling pipeline to use","name":"pipeline","options":["standard","vlm"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"standard"},"separator":{"_input_type":"StrInput","advanced":true,"display_name":"Separator","dynamic":false,"info":"Specify the separator to use between multiple outputs in Message format.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"separator","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"\n\n"},"silent_errors":{"_input_type":"BoolInput","advanced":true,"display_name":"Silent Errors","dynamic":false,"info":"If true, errors will not raise an exception.","list":false,"list_add_label":"Add More","name":"silent_errors","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"DoclingRemote":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Uses Docling to process input documents connecting to your instance of Docling Serve.","display_name":"Docling Serve","documentation":"https://docling-project.github.io/docling/","edited":false,"field_order":["path","file_path","separator","silent_errors","delete_server_file_after_processing","ignore_unsupported_extensions","ignore_unspecified_files","api_url","max_concurrency","max_poll_timeout","api_headers","docling_serve_opts"],"frozen":false,"icon":"Docling","legacy":false,"metadata":{"code_hash":"26eeb513dded","dependencies":{"dependencies":[{"name":"httpx","version":"0.28.1"},{"name":"docling_core","version":"2.54.0"},{"name":"pydantic","version":"2.11.10"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.docling.docling_remote.DoclingRemoteComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Files","group_outputs":false,"method":"load_files","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_headers":{"_input_type":"NestedDictInput","advanced":true,"display_name":"HTTP headers","dynamic":false,"info":"Optional dictionary of additional headers required for connecting to Docling Serve.","list":false,"list_add_label":"Add More","name":"api_headers","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":"NestedDict","value":{}},"api_url":{"_input_type":"StrInput","advanced":false,"display_name":"Server address","dynamic":false,"info":"URL of the Docling Serve instance.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"api_url","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"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 base64\nimport time\nfrom concurrent.futures import Future, ThreadPoolExecutor\nfrom pathlib import Path\nfrom typing import Any\n\nimport httpx\nfrom docling_core.types.doc import DoclingDocument\nfrom pydantic import ValidationError\n\nfrom lfx.base.data import BaseFileComponent\nfrom lfx.inputs import IntInput, NestedDictInput, StrInput\nfrom lfx.inputs.inputs import FloatInput\nfrom lfx.schema import Data\nfrom lfx.utils.util import transform_localhost_url\n\n\nclass DoclingRemoteComponent(BaseFileComponent):\n display_name = \"Docling Serve\"\n description = \"Uses Docling to process input documents connecting to your instance of Docling Serve.\"\n documentation = \"https://docling-project.github.io/docling/\"\n trace_type = \"tool\"\n icon = \"Docling\"\n name = \"DoclingRemote\"\n\n MAX_500_RETRIES = 5\n\n # https://docling-project.github.io/docling/usage/supported_formats/\n VALID_EXTENSIONS = [\n \"adoc\",\n \"asciidoc\",\n \"asc\",\n \"bmp\",\n \"csv\",\n \"dotx\",\n \"dotm\",\n \"docm\",\n \"docx\",\n \"htm\",\n \"html\",\n \"jpeg\",\n \"json\",\n \"md\",\n \"pdf\",\n \"png\",\n \"potx\",\n \"ppsx\",\n \"pptm\",\n \"potm\",\n \"ppsm\",\n \"pptx\",\n \"tiff\",\n \"txt\",\n \"xls\",\n \"xlsx\",\n \"xhtml\",\n \"xml\",\n \"webp\",\n ]\n\n inputs = [\n *BaseFileComponent.get_base_inputs(),\n StrInput(\n name=\"api_url\",\n display_name=\"Server address\",\n info=\"URL of the Docling Serve instance.\",\n required=True,\n ),\n IntInput(\n name=\"max_concurrency\",\n display_name=\"Concurrency\",\n info=\"Maximum number of concurrent requests for the server.\",\n advanced=True,\n value=2,\n ),\n FloatInput(\n name=\"max_poll_timeout\",\n display_name=\"Maximum poll time\",\n info=\"Maximum waiting time for the document conversion to complete.\",\n advanced=True,\n value=3600,\n ),\n NestedDictInput(\n name=\"api_headers\",\n display_name=\"HTTP headers\",\n advanced=True,\n required=False,\n info=(\"Optional dictionary of additional headers required for connecting to Docling Serve.\"),\n ),\n NestedDictInput(\n name=\"docling_serve_opts\",\n display_name=\"Docling options\",\n advanced=True,\n required=False,\n info=(\n \"Optional dictionary of additional options. \"\n \"See https://github.com/docling-project/docling-serve/blob/main/docs/usage.md for more information.\"\n ),\n ),\n ]\n\n outputs = [\n *BaseFileComponent.get_base_outputs(),\n ]\n\n def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[BaseFileComponent.BaseFile]:\n # Transform localhost URLs to container-accessible hosts when running in a container\n transformed_url = transform_localhost_url(self.api_url)\n base_url = f\"{transformed_url}/v1\"\n\n def _convert_document(client: httpx.Client, file_path: Path, options: dict[str, Any]) -> Data | None:\n encoded_doc = base64.b64encode(file_path.read_bytes()).decode()\n payload = {\n \"options\": options,\n \"sources\": [{\"kind\": \"file\", \"base64_string\": encoded_doc, \"filename\": file_path.name}],\n }\n\n response = client.post(f\"{base_url}/convert/source/async\", json=payload)\n response.raise_for_status()\n task = response.json()\n\n http_failures = 0\n retry_status_start = 500\n retry_status_end = 600\n start_wait_time = time.monotonic()\n while task[\"task_status\"] not in (\"success\", \"failure\"):\n # Check if processing exceeds the maximum poll timeout\n processing_time = time.monotonic() - start_wait_time\n if processing_time >= self.max_poll_timeout:\n msg = (\n f\"Processing time {processing_time=} exceeds the maximum poll timeout {self.max_poll_timeout=}.\"\n \"Please increase the max_poll_timeout parameter or review why the processing \"\n \"takes long on the server.\"\n )\n self.log(msg)\n raise RuntimeError(msg)\n\n # Call for a new status update\n time.sleep(2)\n response = client.get(f\"{base_url}/status/poll/{task['task_id']}\")\n\n # Check if the status call gets into 5xx errors and retry\n if retry_status_start <= response.status_code < retry_status_end:\n http_failures += 1\n if http_failures > self.MAX_500_RETRIES:\n self.log(f\"The status requests got a http response {response.status_code} too many times.\")\n return None\n continue\n\n # Update task status\n task = response.json()\n\n result_resp = client.get(f\"{base_url}/result/{task['task_id']}\")\n result_resp.raise_for_status()\n result = result_resp.json()\n\n if \"json_content\" not in result[\"document\"] or result[\"document\"][\"json_content\"] is None:\n self.log(\"No JSON DoclingDocument found in the result.\")\n return None\n\n try:\n doc = DoclingDocument.model_validate(result[\"document\"][\"json_content\"])\n return Data(data={\"doc\": doc, \"file_path\": str(file_path)})\n except ValidationError as e:\n self.log(f\"Error validating the document. {e}\")\n return None\n\n docling_options = {\n \"to_formats\": [\"json\"],\n \"image_export_mode\": \"placeholder\",\n **(self.docling_serve_opts or {}),\n }\n\n processed_data: list[Data | None] = []\n with (\n httpx.Client(headers=self.api_headers) as client,\n ThreadPoolExecutor(max_workers=self.max_concurrency) as executor,\n ):\n futures: list[tuple[int, Future]] = []\n for i, file in enumerate(file_list):\n if file.path is None:\n processed_data.append(None)\n continue\n\n futures.append((i, executor.submit(_convert_document, client, file.path, docling_options)))\n\n for _index, future in futures:\n try:\n result_data = future.result()\n processed_data.append(result_data)\n except (httpx.HTTPStatusError, httpx.RequestError, KeyError, ValueError) as exc:\n self.log(f\"Docling remote processing failed: {exc}\")\n raise\n\n return self.rollup_data(file_list, processed_data)\n"},"delete_server_file_after_processing":{"_input_type":"BoolInput","advanced":true,"display_name":"Delete Server File After Processing","dynamic":false,"info":"If true, the Server File Path will be deleted after processing.","list":false,"list_add_label":"Add More","name":"delete_server_file_after_processing","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"docling_serve_opts":{"_input_type":"NestedDictInput","advanced":true,"display_name":"Docling options","dynamic":false,"info":"Optional dictionary of additional options. See https://github.com/docling-project/docling-serve/blob/main/docs/usage.md for more information.","list":false,"list_add_label":"Add More","name":"docling_serve_opts","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":"NestedDict","value":{}},"file_path":{"_input_type":"HandleInput","advanced":true,"display_name":"Server File Path","dynamic":false,"info":"Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.","input_types":["Data","Message"],"list":true,"list_add_label":"Add More","name":"file_path","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"ignore_unspecified_files":{"_input_type":"BoolInput","advanced":true,"display_name":"Ignore Unspecified Files","dynamic":false,"info":"If true, Data with no 'file_path' property will be ignored.","list":false,"list_add_label":"Add More","name":"ignore_unspecified_files","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"ignore_unsupported_extensions":{"_input_type":"BoolInput","advanced":true,"display_name":"Ignore Unsupported Extensions","dynamic":false,"info":"If true, files with unsupported extensions will not be processed.","list":false,"list_add_label":"Add More","name":"ignore_unsupported_extensions","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"max_concurrency":{"_input_type":"IntInput","advanced":true,"display_name":"Concurrency","dynamic":false,"info":"Maximum number of concurrent requests for the server.","list":false,"list_add_label":"Add More","name":"max_concurrency","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":2},"max_poll_timeout":{"_input_type":"FloatInput","advanced":true,"display_name":"Maximum poll time","dynamic":false,"info":"Maximum waiting time for the document conversion to complete.","list":false,"list_add_label":"Add More","name":"max_poll_timeout","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":3600.0},"path":{"_input_type":"FileInput","advanced":false,"display_name":"Files","dynamic":false,"fileTypes":["adoc","asciidoc","asc","bmp","csv","dotx","dotm","docm","docx","htm","html","jpeg","json","md","pdf","png","potx","ppsx","pptm","potm","ppsm","pptx","tiff","txt","xls","xlsx","xhtml","xml","webp","zip","tar","tgz","bz2","gz"],"file_path":"","info":"Supported file extensions: adoc, asciidoc, asc, bmp, csv, dotx, dotm, docm, docx, htm, html, jpeg, json, md, pdf, png, potx, ppsx, pptm, potm, ppsm, pptx, tiff, txt, xls, xlsx, xhtml, xml, webp; optionally bundled in file extensions: zip, tar, tgz, bz2, gz","list":true,"list_add_label":"Add More","name":"path","override_skip":false,"placeholder":"","required":false,"show":true,"temp_file":false,"title_case":false,"tool_mode":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"file","value":[]},"separator":{"_input_type":"StrInput","advanced":true,"display_name":"Separator","dynamic":false,"info":"Specify the separator to use between multiple outputs in Message format.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"separator","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"\n\n"},"silent_errors":{"_input_type":"BoolInput","advanced":true,"display_name":"Silent Errors","dynamic":false,"info":"If true, errors will not raise an exception.","list":false,"list_add_label":"Add More","name":"silent_errors","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"ExportDoclingDocument":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Export DoclingDocument to markdown, html or other formats.","display_name":"Export DoclingDocument","documentation":"https://docling-project.github.io/docling/","edited":false,"field_order":["data_inputs","export_format","image_mode","md_image_placeholder","md_page_break_placeholder","doc_key"],"frozen":false,"icon":"Docling","legacy":false,"metadata":{"code_hash":"32577a7e396b","dependencies":{"dependencies":[{"name":"docling_core","version":"2.54.0"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.docling.export_docling_document.ExportDoclingDocumentComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Exported data","group_outputs":false,"method":"export_document","name":"data","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from typing import Any\n\nfrom docling_core.types.doc import ImageRefMode\n\nfrom lfx.base.data.docling_utils import extract_docling_documents\nfrom lfx.custom import Component\nfrom lfx.io import DropdownInput, HandleInput, MessageTextInput, Output, StrInput\nfrom lfx.schema import Data, DataFrame\n\n\nclass ExportDoclingDocumentComponent(Component):\n display_name: str = \"Export DoclingDocument\"\n description: str = \"Export DoclingDocument to markdown, html or other formats.\"\n documentation = \"https://docling-project.github.io/docling/\"\n icon = \"Docling\"\n name = \"ExportDoclingDocument\"\n\n inputs = [\n HandleInput(\n name=\"data_inputs\",\n display_name=\"Data or DataFrame\",\n info=\"The data with documents to export.\",\n input_types=[\"Data\", \"DataFrame\"],\n required=True,\n ),\n DropdownInput(\n name=\"export_format\",\n display_name=\"Export format\",\n options=[\"Markdown\", \"HTML\", \"Plaintext\", \"DocTags\"],\n info=\"Select the export format to convert the input.\",\n value=\"Markdown\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"image_mode\",\n display_name=\"Image export mode\",\n options=[\"placeholder\", \"embedded\"],\n info=(\n \"Specify how images are exported in the output. Placeholder will replace the images with a string, \"\n \"whereas Embedded will include them as base64 encoded images.\"\n ),\n value=\"placeholder\",\n ),\n StrInput(\n name=\"md_image_placeholder\",\n display_name=\"Image placeholder\",\n info=\"Specify the image placeholder for markdown exports.\",\n value=\"\",\n advanced=True,\n ),\n StrInput(\n name=\"md_page_break_placeholder\",\n display_name=\"Page break placeholder\",\n info=\"Add this placeholder betweek pages in the markdown output.\",\n value=\"\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"doc_key\",\n display_name=\"Doc Key\",\n info=\"The key to use for the DoclingDocument column.\",\n value=\"doc\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Exported data\", name=\"data\", method=\"export_document\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:\n if field_name == \"export_format\" and field_value == \"Markdown\":\n build_config[\"md_image_placeholder\"][\"show\"] = True\n build_config[\"md_page_break_placeholder\"][\"show\"] = True\n build_config[\"image_mode\"][\"show\"] = True\n elif field_name == \"export_format\" and field_value == \"HTML\":\n build_config[\"md_image_placeholder\"][\"show\"] = False\n build_config[\"md_page_break_placeholder\"][\"show\"] = False\n build_config[\"image_mode\"][\"show\"] = True\n elif field_name == \"export_format\" and field_value in {\"Plaintext\", \"DocTags\"}:\n build_config[\"md_image_placeholder\"][\"show\"] = False\n build_config[\"md_page_break_placeholder\"][\"show\"] = False\n build_config[\"image_mode\"][\"show\"] = False\n\n return build_config\n\n def export_document(self) -> list[Data]:\n documents, warning = extract_docling_documents(self.data_inputs, self.doc_key)\n if warning:\n self.status = warning\n\n results: list[Data] = []\n try:\n image_mode = ImageRefMode(self.image_mode)\n for doc in documents:\n content = \"\"\n if self.export_format == \"Markdown\":\n content = doc.export_to_markdown(\n image_mode=image_mode,\n image_placeholder=self.md_image_placeholder,\n page_break_placeholder=self.md_page_break_placeholder,\n )\n elif self.export_format == \"HTML\":\n content = doc.export_to_html(image_mode=image_mode)\n elif self.export_format == \"Plaintext\":\n content = doc.export_to_text()\n elif self.export_format == \"DocTags\":\n content = doc.export_to_doctags()\n\n results.append(Data(text=content))\n except Exception as e:\n msg = f\"Error splitting text: {e}\"\n raise TypeError(msg) from e\n\n return results\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.export_document())\n"},"data_inputs":{"_input_type":"HandleInput","advanced":false,"display_name":"Data or DataFrame","dynamic":false,"info":"The data with documents to export.","input_types":["Data","DataFrame"],"list":false,"list_add_label":"Add More","name":"data_inputs","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"doc_key":{"_input_type":"MessageTextInput","advanced":true,"display_name":"Doc Key","dynamic":false,"info":"The key to use for the DoclingDocument column.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"doc_key","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":"doc"},"export_format":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Export format","dynamic":false,"external_options":{},"info":"Select the export format to convert the input.","name":"export_format","options":["Markdown","HTML","Plaintext","DocTags"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Markdown"},"image_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Image export mode","dynamic":false,"external_options":{},"info":"Specify how images are exported in the output. Placeholder will replace the images with a string, whereas Embedded will include them as base64 encoded images.","name":"image_mode","options":["placeholder","embedded"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"placeholder"},"md_image_placeholder":{"_input_type":"StrInput","advanced":true,"display_name":"Image placeholder","dynamic":false,"info":"Specify the image placeholder for markdown exports.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"md_image_placeholder","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"md_page_break_placeholder":{"_input_type":"StrInput","advanced":true,"display_name":"Page break placeholder","dynamic":false,"info":"Add this placeholder betweek pages in the markdown output.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"md_page_break_placeholder","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false}}],["duckduckgo",{"DuckDuckGoSearchComponent":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Search the web using DuckDuckGo with customizable result limits","display_name":"DuckDuckGo Search","documentation":"https://python.langchain.com/docs/integrations/tools/ddg","edited":false,"field_order":["input_value","max_results","max_snippet_length"],"frozen":false,"icon":"DuckDuckGo","legacy":false,"metadata":{"code_hash":"2e522a5a4389","dependencies":{"dependencies":[{"name":"langchain_community","version":"0.3.21"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.duckduckgo.duck_duck_go_search_run.DuckDuckGoSearchComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"fetch_content_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from langchain_community.tools import DuckDuckGoSearchRun\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.inputs.inputs import IntInput, MessageTextInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.template.field.base import Output\n\n\nclass DuckDuckGoSearchComponent(Component):\n \"\"\"Component for performing web searches using DuckDuckGo.\"\"\"\n\n display_name = \"DuckDuckGo Search\"\n description = \"Search the web using DuckDuckGo with customizable result limits\"\n documentation = \"https://python.langchain.com/docs/integrations/tools/ddg\"\n icon = \"DuckDuckGo\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Search Query\",\n required=True,\n info=\"The search query to execute with DuckDuckGo\",\n tool_mode=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n value=5,\n required=False,\n advanced=True,\n info=\"Maximum number of search results to return\",\n ),\n IntInput(\n name=\"max_snippet_length\",\n display_name=\"Max Snippet Length\",\n value=100,\n required=False,\n advanced=True,\n info=\"Maximum length of each result snippet\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"fetch_content_dataframe\"),\n ]\n\n def _build_wrapper(self) -> DuckDuckGoSearchRun:\n \"\"\"Build the DuckDuckGo search wrapper.\"\"\"\n return DuckDuckGoSearchRun()\n\n def run_model(self) -> DataFrame:\n return self.fetch_content_dataframe()\n\n def fetch_content(self) -> list[Data]:\n \"\"\"Execute the search and return results as Data objects.\"\"\"\n try:\n wrapper = self._build_wrapper()\n\n full_results = wrapper.run(f\"{self.input_value} (site:*)\")\n\n result_list = full_results.split(\"\\n\")[: self.max_results]\n\n data_results = []\n for result in result_list:\n if result.strip():\n snippet = result[: self.max_snippet_length]\n data_results.append(\n Data(\n text=snippet,\n data={\n \"content\": result,\n \"snippet\": snippet,\n },\n )\n )\n except (ValueError, AttributeError) as e:\n error_data = [Data(text=str(e), data={\"error\": str(e)})]\n self.status = error_data\n return error_data\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_dataframe(self) -> DataFrame:\n \"\"\"Convert the search results to a DataFrame.\n\n Returns:\n DataFrame: A DataFrame containing the search results.\n \"\"\"\n data = self.fetch_content()\n return DataFrame(data)\n"},"input_value":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"The search query to execute with DuckDuckGo","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"input_value","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"max_results":{"_input_type":"IntInput","advanced":true,"display_name":"Max Results","dynamic":false,"info":"Maximum number of search results to return","list":false,"list_add_label":"Add More","name":"max_results","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":5},"max_snippet_length":{"_input_type":"IntInput","advanced":true,"display_name":"Max Snippet Length","dynamic":false,"info":"Maximum length of each result snippet","list":false,"list_add_label":"Add More","name":"max_snippet_length","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":100}},"tool_mode":false}}],["elastic",{"Elasticsearch":{"base_classes":["Data","DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Elasticsearch Vector Store with with advanced, customizable search capabilities.","display_name":"Elasticsearch","documentation":"","edited":false,"field_order":["elasticsearch_url","cloud_id","index_name","ingest_data","search_query","should_cache_vector_store","username","password","embedding","search_type","number_of_results","search_score_threshold","api_key","verify_certs"],"frozen":false,"icon":"ElasticsearchStore","legacy":false,"metadata":{"code_hash":"23ea4383039e","dependencies":{"dependencies":[{"name":"elasticsearch","version":"8.16.0"},{"name":"langchain_core","version":"0.3.80"},{"name":"langchain_elasticsearch","version":"0.4.0"},{"name":"lfx","version":null}],"total_dependencies":4},"module":"lfx.components.elastic.elasticsearch.ElasticsearchVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","api_key":{"_input_type":"SecretStrInput","advanced":true,"display_name":"Elastic API Key","dynamic":false,"info":"API Key for Elastic Cloud authentication. If used, 'username' and 'password' are not required.","input_types":[],"load_from_db":true,"name":"api_key","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"cloud_id":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Elastic Cloud ID","dynamic":false,"info":"Use this for Elastic Cloud deployments. Do not use together with 'Elasticsearch URL'.","input_types":[],"load_from_db":true,"name":"cloud_id","override_skip":false,"password":true,"placeholder":"","required":false,"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":"from typing import Any\n\nfrom elasticsearch import Elasticsearch\nfrom langchain_core.documents import Document\nfrom langchain_elasticsearch import ElasticsearchStore\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.io import (\n BoolInput,\n DropdownInput,\n FloatInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom lfx.schema.data import Data\n\n\nclass ElasticsearchVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"Elasticsearch Vector Store with with advanced, customizable search capabilities.\"\"\"\n\n display_name: str = \"Elasticsearch\"\n description: str = \"Elasticsearch Vector Store with with advanced, customizable search capabilities.\"\n name = \"Elasticsearch\"\n icon = \"ElasticsearchStore\"\n\n inputs = [\n StrInput(\n name=\"elasticsearch_url\",\n display_name=\"Elasticsearch URL\",\n value=\"http://localhost:9200\",\n info=\"URL for self-managed Elasticsearch deployments (e.g., http://localhost:9200). \"\n \"Do not use with Elastic Cloud deployments, use Elastic Cloud ID instead.\",\n ),\n SecretStrInput(\n name=\"cloud_id\",\n display_name=\"Elastic Cloud ID\",\n value=\"\",\n info=\"Use this for Elastic Cloud deployments. Do not use together with 'Elasticsearch URL'.\",\n ),\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow\",\n info=\"The index name where the vectors will be stored in Elasticsearch cluster.\",\n ),\n *LCVectorStoreComponent.inputs,\n StrInput(\n name=\"username\",\n display_name=\"Username\",\n value=\"\",\n advanced=False,\n info=(\n \"Elasticsearch username (e.g., 'elastic'). \"\n \"Required for both local and Elastic Cloud setups unless API keys are used.\"\n ),\n ),\n SecretStrInput(\n name=\"password\",\n display_name=\"Elasticsearch Password\",\n value=\"\",\n advanced=False,\n info=(\n \"Elasticsearch password for the specified user. \"\n \"Required for both local and Elastic Cloud setups unless API keys are used.\"\n ),\n ),\n HandleInput(\n name=\"embedding\",\n display_name=\"Embedding\",\n input_types=[\"Embeddings\"],\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n options=[\"similarity\", \"mmr\"],\n value=\"similarity\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results.\",\n value=0.0,\n advanced=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Elastic API Key\",\n value=\"\",\n advanced=True,\n info=\"API Key for Elastic Cloud authentication. If used, 'username' and 'password' are not required.\",\n ),\n BoolInput(\n name=\"verify_certs\",\n display_name=\"Verify SSL Certificates\",\n value=True,\n advanced=True,\n info=\"Whether to verify SSL certificates when connecting to Elasticsearch.\",\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self) -> ElasticsearchStore:\n \"\"\"Builds the Elasticsearch Vector Store object.\"\"\"\n if self.cloud_id and self.elasticsearch_url:\n msg = (\n \"Both 'cloud_id' and 'elasticsearch_url' provided. \"\n \"Please use only one based on your deployment (Cloud or Local).\"\n )\n raise ValueError(msg)\n\n es_params = {\n \"index_name\": self.index_name,\n \"embedding\": self.embedding,\n \"es_user\": self.username or None,\n \"es_password\": self.password or None,\n }\n\n if self.cloud_id:\n es_params[\"es_cloud_id\"] = self.cloud_id\n else:\n es_params[\"es_url\"] = self.elasticsearch_url\n\n if self.api_key:\n es_params[\"api_key\"] = self.api_key\n\n # Check if we need to verify SSL certificates\n if self.verify_certs is False:\n # Build client parameters for Elasticsearch constructor\n client_params: dict[str, Any] = {}\n client_params[\"verify_certs\"] = False\n\n if self.cloud_id:\n client_params[\"cloud_id\"] = self.cloud_id\n else:\n client_params[\"hosts\"] = [self.elasticsearch_url]\n\n if self.api_key:\n client_params[\"api_key\"] = self.api_key\n elif self.username and self.password:\n client_params[\"basic_auth\"] = (self.username, self.password)\n\n es_client = Elasticsearch(**client_params)\n es_params[\"es_connection\"] = es_client\n\n elasticsearch = ElasticsearchStore(**es_params)\n\n # If documents are provided, add them to the store\n if self.ingest_data:\n documents = self._prepare_documents()\n if documents:\n elasticsearch.add_documents(documents)\n\n return elasticsearch\n\n def _prepare_documents(self) -> list[Document]:\n \"\"\"Prepares documents from the input data to add to the vector store.\"\"\"\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for data in self.ingest_data:\n if isinstance(data, Data):\n documents.append(data.to_lc_document())\n else:\n error_message = \"Vector Store Inputs must be Data objects.\"\n self.log(error_message)\n raise TypeError(error_message)\n return documents\n\n def _add_documents_to_vector_store(self, vector_store: \"ElasticsearchStore\") -> None:\n \"\"\"Adds documents to the Vector Store.\"\"\"\n documents = self._prepare_documents()\n if documents and self.embedding:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n vector_store.add_documents(documents)\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def search(self, query: str | None = None) -> list[dict[str, Any]]:\n \"\"\"Search for similar documents in the vector store or retrieve all documents if no query is provided.\"\"\"\n vector_store = self.build_vector_store()\n search_kwargs = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if query:\n search_type = self.search_type.lower()\n if search_type not in {\"similarity\", \"mmr\"}:\n msg = f\"Invalid search type: {self.search_type}\"\n self.log(msg)\n raise ValueError(msg)\n try:\n if search_type == \"similarity\":\n results = vector_store.similarity_search_with_score(query, **search_kwargs)\n elif search_type == \"mmr\":\n results = vector_store.max_marginal_relevance_search(query, **search_kwargs)\n except Exception as e:\n msg = (\n \"Error occurred while querying the Elasticsearch VectorStore,\"\n \" there is no Data into the VectorStore.\"\n )\n self.log(msg)\n raise ValueError(msg) from e\n return [\n {\"page_content\": doc.page_content, \"metadata\": doc.metadata, \"score\": score} for doc, score in results\n ]\n results = self.get_all_documents(vector_store, **search_kwargs)\n return [{\"page_content\": doc.page_content, \"metadata\": doc.metadata, \"score\": score} for doc, score in results]\n\n def get_all_documents(self, vector_store: ElasticsearchStore, **kwargs) -> list[tuple[Document, float]]:\n \"\"\"Retrieve all documents from the vector store.\"\"\"\n client = vector_store.client\n index_name = self.index_name\n\n query = {\n \"query\": {\"match_all\": {}},\n \"size\": kwargs.get(\"k\", self.number_of_results),\n }\n\n response = client.search(index=index_name, body=query)\n\n results = []\n for hit in response[\"hits\"][\"hits\"]:\n doc = Document(\n page_content=hit[\"_source\"].get(\"text\", \"\"),\n metadata=hit[\"_source\"].get(\"metadata\", {}),\n )\n score = hit[\"_score\"]\n results.append((doc, score))\n\n return results\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search for documents in the vector store based on the search input.\n\n If no search input is provided, retrieve all documents.\n \"\"\"\n results = self.search(self.search_query)\n retrieved_data = [\n Data(\n text=result[\"page_content\"],\n file_path=result[\"metadata\"].get(\"file_path\", \"\"),\n )\n for result in results\n ]\n self.status = retrieved_data\n return retrieved_data\n\n def get_retriever_kwargs(self):\n \"\"\"Get the keyword arguments for the retriever.\"\"\"\n return {\n \"search_type\": self.search_type.lower(),\n \"search_kwargs\": {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n },\n }\n"},"elasticsearch_url":{"_input_type":"StrInput","advanced":false,"display_name":"Elasticsearch URL","dynamic":false,"info":"URL for self-managed Elasticsearch deployments (e.g., http://localhost:9200). Do not use with Elastic Cloud deployments, use Elastic Cloud ID instead.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"elasticsearch_url","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"http://localhost:9200"},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"index_name":{"_input_type":"StrInput","advanced":false,"display_name":"Index Name","dynamic":false,"info":"The index name where the vectors will be stored in Elasticsearch cluster.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"index_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"langflow"},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Number of Results","dynamic":false,"info":"Number of results to return.","list":false,"list_add_label":"Add More","name":"number_of_results","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":4},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Elasticsearch Password","dynamic":false,"info":"Elasticsearch password for the specified user. Required for both local and Elastic Cloud setups unless API keys are used.","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"search_score_threshold":{"_input_type":"FloatInput","advanced":true,"display_name":"Search Score Threshold","dynamic":false,"info":"Minimum similarity score threshold for search results.","list":false,"list_add_label":"Add More","name":"search_score_threshold","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"float","value":0.0},"search_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Search Type","dynamic":false,"external_options":{},"info":"","name":"search_type","options":["similarity","mmr"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"similarity"},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"Elasticsearch username (e.g., 'elastic'). Required for both local and Elastic Cloud setups unless API keys are used.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"verify_certs":{"_input_type":"BoolInput","advanced":true,"display_name":"Verify SSL Certificates","dynamic":false,"info":"Whether to verify SSL certificates when connecting to Elasticsearch.","list":false,"list_add_label":"Add More","name":"verify_certs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false},"OpenSearchVectorStoreComponent":{"base_classes":["Data","DataFrame","VectorStore"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Store and search documents using OpenSearch with hybrid semantic and keyword search capabilities.","display_name":"OpenSearch","documentation":"","edited":false,"field_order":["docs_metadata","opensearch_url","index_name","engine","space_type","ef_construction","m","ingest_data","search_query","should_cache_vector_store","embedding","vector_field","number_of_results","filter_expression","auth_mode","username","password","jwt_token","jwt_header","bearer_prefix","use_ssl","verify_certs"],"frozen":false,"icon":"OpenSearch","legacy":false,"metadata":{"code_hash":"77834dd0fa75","dependencies":{"dependencies":[{"name":"opensearchpy","version":"2.8.0"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.elastic.opensearch.OpenSearchVectorStoreComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Vector Store Connection","group_outputs":false,"hidden":false,"method":"as_vector_store","name":"vectorstoreconnection","selected":"VectorStore","tool_mode":true,"types":["VectorStore"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Authentication Mode","dynamic":false,"external_options":{},"info":"Authentication method: 'basic' for username/password authentication, or 'jwt' for JSON Web Token (Bearer) authentication.","name":"auth_mode","options":["basic","jwt"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"basic"},"bearer_prefix":{"_input_type":"BoolInput","advanced":true,"display_name":"Prefix 'Bearer '","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"bearer_prefix","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from __future__ import annotations\n\nimport json\nimport uuid\nfrom typing import Any\n\nfrom opensearchpy import OpenSearch, helpers\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.base.vectorstores.vector_store_connection_decorator import vector_store_connection\nfrom lfx.io import BoolInput, DropdownInput, HandleInput, IntInput, MultilineInput, SecretStrInput, StrInput, TableInput\nfrom lfx.log import logger\nfrom lfx.schema.data import Data\n\n\n@vector_store_connection\nclass OpenSearchVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"OpenSearch Vector Store Component with Hybrid Search Capabilities.\n\n This component provides vector storage and retrieval using OpenSearch, combining semantic\n similarity search (KNN) with keyword-based search for optimal results. It supports document\n ingestion, vector embeddings, and advanced filtering with authentication options.\n\n Features:\n - Vector storage with configurable engines (jvector, nmslib, faiss, lucene)\n - Hybrid search combining KNN vector similarity and keyword matching\n - Flexible authentication (Basic auth, JWT tokens)\n - Advanced filtering and aggregations\n - Metadata injection during document ingestion\n \"\"\"\n\n display_name: str = \"OpenSearch\"\n icon: str = \"OpenSearch\"\n description: str = (\n \"Store and search documents using OpenSearch with hybrid semantic and keyword search capabilities.\"\n )\n\n # Keys we consider baseline\n default_keys: list[str] = [\n \"opensearch_url\",\n \"index_name\",\n *[i.name for i in LCVectorStoreComponent.inputs], # search_query, add_documents, etc.\n \"embedding\",\n \"vector_field\",\n \"number_of_results\",\n \"auth_mode\",\n \"username\",\n \"password\",\n \"jwt_token\",\n \"jwt_header\",\n \"bearer_prefix\",\n \"use_ssl\",\n \"verify_certs\",\n \"filter_expression\",\n \"engine\",\n \"space_type\",\n \"ef_construction\",\n \"m\",\n \"docs_metadata\",\n ]\n\n inputs = [\n TableInput(\n name=\"docs_metadata\",\n display_name=\"Document Metadata\",\n info=(\n \"Additional metadata key-value pairs to be added to all ingested documents. \"\n \"Useful for tagging documents with source information, categories, or other custom attributes.\"\n ),\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Key name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Value of the metadata\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n ),\n StrInput(\n name=\"opensearch_url\",\n display_name=\"OpenSearch URL\",\n value=\"http://localhost:9200\",\n info=(\n \"The connection URL for your OpenSearch cluster \"\n \"(e.g., http://localhost:9200 for local development or your cloud endpoint).\"\n ),\n ),\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow\",\n info=(\n \"The OpenSearch index name where documents will be stored and searched. \"\n \"Will be created automatically if it doesn't exist.\"\n ),\n ),\n DropdownInput(\n name=\"engine\",\n display_name=\"Vector Engine\",\n options=[\"jvector\", \"nmslib\", \"faiss\", \"lucene\"],\n value=\"jvector\",\n info=(\n \"Vector search engine for similarity calculations. 'jvector' is recommended for most use cases. \"\n \"Note: Amazon OpenSearch Serverless only supports 'nmslib' or 'faiss'.\"\n ),\n advanced=True,\n ),\n DropdownInput(\n name=\"space_type\",\n display_name=\"Distance Metric\",\n options=[\"l2\", \"l1\", \"cosinesimil\", \"linf\", \"innerproduct\"],\n value=\"l2\",\n info=(\n \"Distance metric for calculating vector similarity. 'l2' (Euclidean) is most common, \"\n \"'cosinesimil' for cosine similarity, 'innerproduct' for dot product.\"\n ),\n advanced=True,\n ),\n IntInput(\n name=\"ef_construction\",\n display_name=\"EF Construction\",\n value=512,\n info=(\n \"Size of the dynamic candidate list during index construction. \"\n \"Higher values improve recall but increase indexing time and memory usage.\"\n ),\n advanced=True,\n ),\n IntInput(\n name=\"m\",\n display_name=\"M Parameter\",\n value=16,\n info=(\n \"Number of bidirectional connections for each vector in the HNSW graph. \"\n \"Higher values improve search quality but increase memory usage and indexing time.\"\n ),\n advanced=True,\n ),\n *LCVectorStoreComponent.inputs, # includes search_query, add_documents, etc.\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n StrInput(\n name=\"vector_field\",\n display_name=\"Vector Field Name\",\n value=\"chunk_embedding\",\n advanced=True,\n info=\"Name of the field in OpenSearch documents that stores the vector embeddings for similarity search.\",\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Default Result Limit\",\n value=10,\n advanced=True,\n info=(\n \"Default maximum number of search results to return when no limit is \"\n \"specified in the filter expression.\"\n ),\n ),\n MultilineInput(\n name=\"filter_expression\",\n display_name=\"Search Filters (JSON)\",\n value=\"\",\n info=(\n \"Optional JSON configuration for search filtering, result limits, and score thresholds.\\n\\n\"\n \"Format 1 - Explicit filters:\\n\"\n '{\"filter\": [{\"term\": {\"filename\":\"doc.pdf\"}}, '\n '{\"terms\":{\"owner\":[\"user1\",\"user2\"]}}], \"limit\": 10, \"score_threshold\": 1.6}\\n\\n'\n \"Format 2 - Context-style mapping:\\n\"\n '{\"data_sources\":[\"file.pdf\"], \"document_types\":[\"application/pdf\"], \"owners\":[\"user123\"]}\\n\\n'\n \"Use __IMPOSSIBLE_VALUE__ as placeholder to ignore specific filters.\"\n ),\n ),\n # ----- Auth controls (dynamic) -----\n DropdownInput(\n name=\"auth_mode\",\n display_name=\"Authentication Mode\",\n value=\"basic\",\n options=[\"basic\", \"jwt\"],\n info=(\n \"Authentication method: 'basic' for username/password authentication, \"\n \"or 'jwt' for JSON Web Token (Bearer) authentication.\"\n ),\n real_time_refresh=True,\n advanced=False,\n ),\n StrInput(\n name=\"username\",\n display_name=\"Username\",\n value=\"admin\",\n show=False,\n ),\n SecretStrInput(\n name=\"password\",\n display_name=\"OpenSearch Password\",\n value=\"admin\",\n show=False,\n ),\n SecretStrInput(\n name=\"jwt_token\",\n display_name=\"JWT Token\",\n value=\"JWT\",\n load_from_db=False,\n show=True,\n info=(\n \"Valid JSON Web Token for authentication. \"\n \"Will be sent in the Authorization header (with optional 'Bearer ' prefix).\"\n ),\n ),\n StrInput(\n name=\"jwt_header\",\n display_name=\"JWT Header Name\",\n value=\"Authorization\",\n show=False,\n advanced=True,\n ),\n BoolInput(\n name=\"bearer_prefix\",\n display_name=\"Prefix 'Bearer '\",\n value=True,\n show=False,\n advanced=True,\n ),\n # ----- TLS -----\n BoolInput(\n name=\"use_ssl\",\n display_name=\"Use SSL/TLS\",\n value=True,\n advanced=True,\n info=\"Enable SSL/TLS encryption for secure connections to OpenSearch.\",\n ),\n BoolInput(\n name=\"verify_certs\",\n display_name=\"Verify SSL Certificates\",\n value=False,\n advanced=True,\n info=(\n \"Verify SSL certificates when connecting. \"\n \"Disable for self-signed certificates in development environments.\"\n ),\n ),\n ]\n\n # ---------- helper functions for index management ----------\n def _default_text_mapping(\n self,\n dim: int,\n engine: str = \"jvector\",\n space_type: str = \"l2\",\n ef_search: int = 512,\n ef_construction: int = 100,\n m: int = 16,\n vector_field: str = \"vector_field\",\n ) -> dict[str, Any]:\n \"\"\"Create the default OpenSearch index mapping for vector search.\n\n This method generates the index configuration with k-NN settings optimized\n for approximate nearest neighbor search using the specified vector engine.\n\n Args:\n dim: Dimensionality of the vector embeddings\n engine: Vector search engine (jvector, nmslib, faiss, lucene)\n space_type: Distance metric for similarity calculation\n ef_search: Size of dynamic list used during search\n ef_construction: Size of dynamic list used during index construction\n m: Number of bidirectional links for each vector\n vector_field: Name of the field storing vector embeddings\n\n Returns:\n Dictionary containing OpenSearch index mapping configuration\n \"\"\"\n return {\n \"settings\": {\"index\": {\"knn\": True, \"knn.algo_param.ef_search\": ef_search}},\n \"mappings\": {\n \"properties\": {\n vector_field: {\n \"type\": \"knn_vector\",\n \"dimension\": dim,\n \"method\": {\n \"name\": \"disk_ann\",\n \"space_type\": space_type,\n \"engine\": engine,\n \"parameters\": {\"ef_construction\": ef_construction, \"m\": m},\n },\n }\n }\n },\n }\n\n def _validate_aoss_with_engines(self, *, is_aoss: bool, engine: str) -> None:\n \"\"\"Validate engine compatibility with Amazon OpenSearch Serverless (AOSS).\n\n Amazon OpenSearch Serverless has restrictions on which vector engines\n can be used. This method ensures the selected engine is compatible.\n\n Args:\n is_aoss: Whether the connection is to Amazon OpenSearch Serverless\n engine: The selected vector search engine\n\n Raises:\n ValueError: If AOSS is used with an incompatible engine\n \"\"\"\n if is_aoss and engine not in {\"nmslib\", \"faiss\"}:\n msg = \"Amazon OpenSearch Service Serverless only supports `nmslib` or `faiss` engines\"\n raise ValueError(msg)\n\n def _is_aoss_enabled(self, http_auth: Any) -> bool:\n \"\"\"Determine if Amazon OpenSearch Serverless (AOSS) is being used.\n\n Args:\n http_auth: The HTTP authentication object\n\n Returns:\n True if AOSS is enabled, False otherwise\n \"\"\"\n return http_auth is not None and hasattr(http_auth, \"service\") and http_auth.service == \"aoss\"\n\n def _bulk_ingest_embeddings(\n self,\n client: OpenSearch,\n index_name: str,\n embeddings: list[list[float]],\n texts: list[str],\n metadatas: list[dict] | None = None,\n ids: list[str] | None = None,\n vector_field: str = \"vector_field\",\n text_field: str = \"text\",\n mapping: dict | None = None,\n max_chunk_bytes: int | None = 1 * 1024 * 1024,\n *,\n is_aoss: bool = False,\n ) -> list[str]:\n \"\"\"Efficiently ingest multiple documents with embeddings into OpenSearch.\n\n This method uses bulk operations to insert documents with their vector\n embeddings and metadata into the specified OpenSearch index.\n\n Args:\n client: OpenSearch client instance\n index_name: Target index for document storage\n embeddings: List of vector embeddings for each document\n texts: List of document texts\n metadatas: Optional metadata dictionaries for each document\n ids: Optional document IDs (UUIDs generated if not provided)\n vector_field: Field name for storing vector embeddings\n text_field: Field name for storing document text\n mapping: Optional index mapping configuration\n max_chunk_bytes: Maximum size per bulk request chunk\n is_aoss: Whether using Amazon OpenSearch Serverless\n\n Returns:\n List of document IDs that were successfully ingested\n \"\"\"\n if not mapping:\n mapping = {}\n\n requests = []\n return_ids = []\n\n for i, text in enumerate(texts):\n metadata = metadatas[i] if metadatas else {}\n _id = ids[i] if ids else str(uuid.uuid4())\n request = {\n \"_op_type\": \"index\",\n \"_index\": index_name,\n vector_field: embeddings[i],\n text_field: text,\n **metadata,\n }\n if is_aoss:\n request[\"id\"] = _id\n else:\n request[\"_id\"] = _id\n requests.append(request)\n return_ids.append(_id)\n if metadatas:\n self.log(f\"Sample metadata: {metadatas[0] if metadatas else {}}\")\n helpers.bulk(client, requests, max_chunk_bytes=max_chunk_bytes)\n return return_ids\n\n # ---------- auth / client ----------\n def _build_auth_kwargs(self) -> dict[str, Any]:\n \"\"\"Build authentication configuration for OpenSearch client.\n\n Constructs the appropriate authentication parameters based on the\n selected auth mode (basic username/password or JWT token).\n\n Returns:\n Dictionary containing authentication configuration\n\n Raises:\n ValueError: If required authentication parameters are missing\n \"\"\"\n mode = (self.auth_mode or \"basic\").strip().lower()\n if mode == \"jwt\":\n token = (self.jwt_token or \"\").strip()\n if not token:\n msg = \"Auth Mode is 'jwt' but no jwt_token was provided.\"\n raise ValueError(msg)\n header_name = (self.jwt_header or \"Authorization\").strip()\n header_value = f\"Bearer {token}\" if self.bearer_prefix else token\n return {\"headers\": {header_name: header_value}}\n user = (self.username or \"\").strip()\n pwd = (self.password or \"\").strip()\n if not user or not pwd:\n msg = \"Auth Mode is 'basic' but username/password are missing.\"\n raise ValueError(msg)\n return {\"http_auth\": (user, pwd)}\n\n def build_client(self) -> OpenSearch:\n \"\"\"Create and configure an OpenSearch client instance.\n\n Returns:\n Configured OpenSearch client ready for operations\n \"\"\"\n auth_kwargs = self._build_auth_kwargs()\n return OpenSearch(\n hosts=[self.opensearch_url],\n use_ssl=self.use_ssl,\n verify_certs=self.verify_certs,\n ssl_assert_hostname=False,\n ssl_show_warn=False,\n **auth_kwargs,\n )\n\n @check_cached_vector_store\n def build_vector_store(self) -> OpenSearch:\n # Return raw OpenSearch client as our “vector store.”\n self.log(self.ingest_data)\n client = self.build_client()\n self._add_documents_to_vector_store(client=client)\n return client\n\n # ---------- ingest ----------\n def _add_documents_to_vector_store(self, client: OpenSearch) -> None:\n \"\"\"Process and ingest documents into the OpenSearch vector store.\n\n This method handles the complete document ingestion pipeline:\n - Prepares document data and metadata\n - Generates vector embeddings\n - Creates appropriate index mappings\n - Bulk inserts documents with vectors\n\n Args:\n client: OpenSearch client for performing operations\n \"\"\"\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n docs = self.ingest_data or []\n if not docs:\n self.log(\"No documents to ingest.\")\n return\n\n # Extract texts and metadata from documents\n texts = []\n metadatas = []\n # Process docs_metadata table input into a dict\n additional_metadata = {}\n if hasattr(self, \"docs_metadata\") and self.docs_metadata:\n logger.debug(f\"[LF] Docs metadata {self.docs_metadata}\")\n if isinstance(self.docs_metadata[-1], Data):\n logger.debug(f\"[LF] Docs metadata is a Data object {self.docs_metadata}\")\n self.docs_metadata = self.docs_metadata[-1].data\n logger.debug(f\"[LF] Docs metadata is a Data object {self.docs_metadata}\")\n additional_metadata.update(self.docs_metadata)\n else:\n for item in self.docs_metadata:\n if isinstance(item, dict) and \"key\" in item and \"value\" in item:\n additional_metadata[item[\"key\"]] = item[\"value\"]\n # Replace string \"None\" values with actual None\n for key, value in additional_metadata.items():\n if value == \"None\":\n additional_metadata[key] = None\n logger.debug(f\"[LF] Additional metadata {additional_metadata}\")\n for doc_obj in docs:\n data_copy = json.loads(doc_obj.model_dump_json())\n text = data_copy.pop(doc_obj.text_key, doc_obj.default_value)\n texts.append(text)\n\n # Merge additional metadata from table input\n data_copy.update(additional_metadata)\n\n metadatas.append(data_copy)\n self.log(metadatas)\n if not self.embedding:\n msg = \"Embedding handle is required to embed documents.\"\n raise ValueError(msg)\n\n # Generate embeddings\n vectors = self.embedding.embed_documents(texts)\n\n if not vectors:\n self.log(\"No vectors generated from documents.\")\n return\n\n # Get vector dimension for mapping\n dim = len(vectors[0]) if vectors else 768 # default fallback\n\n # Check for AOSS\n auth_kwargs = self._build_auth_kwargs()\n is_aoss = self._is_aoss_enabled(auth_kwargs.get(\"http_auth\"))\n\n # Validate engine with AOSS\n engine = getattr(self, \"engine\", \"jvector\")\n self._validate_aoss_with_engines(is_aoss=is_aoss, engine=engine)\n\n # Create mapping with proper KNN settings\n space_type = getattr(self, \"space_type\", \"l2\")\n ef_construction = getattr(self, \"ef_construction\", 512)\n m = getattr(self, \"m\", 16)\n\n mapping = self._default_text_mapping(\n dim=dim,\n engine=engine,\n space_type=space_type,\n ef_construction=ef_construction,\n m=m,\n vector_field=self.vector_field,\n )\n\n self.log(f\"Indexing {len(texts)} documents into '{self.index_name}' with proper KNN mapping...\")\n\n # Use the LangChain-style bulk ingestion\n return_ids = self._bulk_ingest_embeddings(\n client=client,\n index_name=self.index_name,\n embeddings=vectors,\n texts=texts,\n metadatas=metadatas,\n vector_field=self.vector_field,\n text_field=\"text\",\n mapping=mapping,\n is_aoss=is_aoss,\n )\n self.log(metadatas)\n\n self.log(f\"Successfully indexed {len(return_ids)} documents.\")\n\n # ---------- helpers for filters ----------\n def _is_placeholder_term(self, term_obj: dict) -> bool:\n # term_obj like {\"filename\": \"__IMPOSSIBLE_VALUE__\"}\n return any(v == \"__IMPOSSIBLE_VALUE__\" for v in term_obj.values())\n\n def _coerce_filter_clauses(self, filter_obj: dict | None) -> list[dict]:\n \"\"\"Convert filter expressions into OpenSearch-compatible filter clauses.\n\n This method accepts two filter formats and converts them to standardized\n OpenSearch query clauses:\n\n Format A - Explicit filters:\n {\"filter\": [{\"term\": {\"field\": \"value\"}}, {\"terms\": {\"field\": [\"val1\", \"val2\"]}}],\n \"limit\": 10, \"score_threshold\": 1.5}\n\n Format B - Context-style mapping:\n {\"data_sources\": [\"file1.pdf\"], \"document_types\": [\"pdf\"], \"owners\": [\"user1\"]}\n\n Args:\n filter_obj: Filter configuration dictionary or None\n\n Returns:\n List of OpenSearch filter clauses (term/terms objects)\n Placeholder values with \"__IMPOSSIBLE_VALUE__\" are ignored\n \"\"\"\n if not filter_obj:\n return []\n\n # If it is a string, try to parse it once\n if isinstance(filter_obj, str):\n try:\n filter_obj = json.loads(filter_obj)\n except json.JSONDecodeError:\n # Not valid JSON - treat as no filters\n return []\n\n # Case A: already an explicit list/dict under \"filter\"\n if \"filter\" in filter_obj:\n raw = filter_obj[\"filter\"]\n if isinstance(raw, dict):\n raw = [raw]\n explicit_clauses: list[dict] = []\n for f in raw or []:\n if \"term\" in f and isinstance(f[\"term\"], dict) and not self._is_placeholder_term(f[\"term\"]):\n explicit_clauses.append(f)\n elif \"terms\" in f and isinstance(f[\"terms\"], dict):\n field, vals = next(iter(f[\"terms\"].items()))\n if isinstance(vals, list) and len(vals) > 0:\n explicit_clauses.append(f)\n return explicit_clauses\n\n # Case B: convert context-style maps into clauses\n field_mapping = {\n \"data_sources\": \"filename\",\n \"document_types\": \"mimetype\",\n \"owners\": \"owner\",\n }\n context_clauses: list[dict] = []\n for k, values in filter_obj.items():\n if not isinstance(values, list):\n continue\n field = field_mapping.get(k, k)\n if len(values) == 0:\n # Match-nothing placeholder (kept to mirror your tool semantics)\n context_clauses.append({\"term\": {field: \"__IMPOSSIBLE_VALUE__\"}})\n elif len(values) == 1:\n if values[0] != \"__IMPOSSIBLE_VALUE__\":\n context_clauses.append({\"term\": {field: values[0]}})\n else:\n context_clauses.append({\"terms\": {field: values}})\n return context_clauses\n\n # ---------- search (single hybrid path matching your tool) ----------\n def search(self, query: str | None = None) -> list[dict[str, Any]]:\n \"\"\"Perform hybrid search combining vector similarity and keyword matching.\n\n This method executes a sophisticated search that combines:\n - K-nearest neighbor (KNN) vector similarity search (70% weight)\n - Multi-field keyword search with fuzzy matching (30% weight)\n - Optional filtering and score thresholds\n - Aggregations for faceted search results\n\n Args:\n query: Search query string (used for both vector embedding and keyword search)\n\n Returns:\n List of search results with page_content, metadata, and relevance scores\n\n Raises:\n ValueError: If embedding component is not provided or filter JSON is invalid\n \"\"\"\n logger.info(self.ingest_data)\n client = self.build_client()\n q = (query or \"\").strip()\n\n # Parse optional filter expression (can be either A or B shape; see _coerce_filter_clauses)\n filter_obj = None\n if getattr(self, \"filter_expression\", \"\") and self.filter_expression.strip():\n try:\n filter_obj = json.loads(self.filter_expression)\n except json.JSONDecodeError as e:\n msg = f\"Invalid filter_expression JSON: {e}\"\n raise ValueError(msg) from e\n\n if not self.embedding:\n msg = \"Embedding is required to run hybrid search (KNN + keyword).\"\n raise ValueError(msg)\n\n # Embed the query\n vec = self.embedding.embed_query(q)\n\n # Build filter clauses (accept both shapes)\n filter_clauses = self._coerce_filter_clauses(filter_obj)\n\n # Respect the tool's limit/threshold defaults\n limit = (filter_obj or {}).get(\"limit\", self.number_of_results)\n score_threshold = (filter_obj or {}).get(\"score_threshold\", 0)\n\n # Build the same hybrid body as your SearchService\n body = {\n \"query\": {\n \"bool\": {\n \"should\": [\n {\n \"knn\": {\n self.vector_field: {\n \"vector\": vec,\n \"k\": 10, # fixed to match the tool\n \"boost\": 0.7,\n }\n }\n },\n {\n \"multi_match\": {\n \"query\": q,\n \"fields\": [\"text^2\", \"filename^1.5\"],\n \"type\": \"best_fields\",\n \"fuzziness\": \"AUTO\",\n \"boost\": 0.3,\n }\n },\n ],\n \"minimum_should_match\": 1,\n }\n },\n \"aggs\": {\n \"data_sources\": {\"terms\": {\"field\": \"filename\", \"size\": 20}},\n \"document_types\": {\"terms\": {\"field\": \"mimetype\", \"size\": 10}},\n \"owners\": {\"terms\": {\"field\": \"owner\", \"size\": 10}},\n },\n \"_source\": [\n \"filename\",\n \"mimetype\",\n \"page\",\n \"text\",\n \"source_url\",\n \"owner\",\n \"allowed_users\",\n \"allowed_groups\",\n ],\n \"size\": limit,\n }\n if filter_clauses:\n body[\"query\"][\"bool\"][\"filter\"] = filter_clauses\n\n if isinstance(score_threshold, (int, float)) and score_threshold > 0:\n # top-level min_score (matches your tool)\n body[\"min_score\"] = score_threshold\n\n resp = client.search(index=self.index_name, body=body)\n hits = resp.get(\"hits\", {}).get(\"hits\", [])\n return [\n {\n \"page_content\": hit[\"_source\"].get(\"text\", \"\"),\n \"metadata\": {k: v for k, v in hit[\"_source\"].items() if k != \"text\"},\n \"score\": hit.get(\"_score\"),\n }\n for hit in hits\n ]\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search documents and return results as Data objects.\n\n This is the main interface method that performs the search using the\n configured search_query and returns results in Langflow's Data format.\n\n Returns:\n List of Data objects containing search results with text and metadata\n\n Raises:\n Exception: If search operation fails\n \"\"\"\n try:\n raw = self.search(self.search_query or \"\")\n return [Data(text=hit[\"page_content\"], **hit[\"metadata\"]) for hit in raw]\n self.log(self.ingest_data)\n except Exception as e:\n self.log(f\"search_documents error: {e}\")\n raise\n\n # -------- dynamic UI handling (auth switch) --------\n async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update component configuration based on field changes.\n\n This method handles real-time UI updates, particularly for authentication\n mode changes that show/hide relevant input fields.\n\n Args:\n build_config: Current component configuration\n field_value: New value for the changed field\n field_name: Name of the field that changed\n\n Returns:\n Updated build configuration with appropriate field visibility\n \"\"\"\n try:\n if field_name == \"auth_mode\":\n mode = (field_value or \"basic\").strip().lower()\n is_basic = mode == \"basic\"\n is_jwt = mode == \"jwt\"\n\n build_config[\"username\"][\"show\"] = is_basic\n build_config[\"password\"][\"show\"] = is_basic\n\n build_config[\"jwt_token\"][\"show\"] = is_jwt\n build_config[\"jwt_header\"][\"show\"] = is_jwt\n build_config[\"bearer_prefix\"][\"show\"] = is_jwt\n\n build_config[\"username\"][\"required\"] = is_basic\n build_config[\"password\"][\"required\"] = is_basic\n\n build_config[\"jwt_token\"][\"required\"] = is_jwt\n build_config[\"jwt_header\"][\"required\"] = is_jwt\n build_config[\"bearer_prefix\"][\"required\"] = False\n\n if is_basic:\n build_config[\"jwt_token\"][\"value\"] = \"\"\n\n return build_config\n\n except (KeyError, ValueError) as e:\n self.log(f\"update_build_config error: {e}\")\n\n return build_config\n"},"docs_metadata":{"_input_type":"TableInput","advanced":false,"display_name":"Document Metadata","dynamic":false,"info":"Additional metadata key-value pairs to be added to all ingested documents. Useful for tagging documents with source information, categories, or other custom attributes.","input_types":["Data"],"is_list":true,"list_add_label":"Add More","name":"docs_metadata","override_skip":false,"placeholder":"","required":false,"show":true,"table_icon":"Table","table_schema":[{"description":"Key name","display_name":"Key","name":"key","type":"str"},{"description":"Value of the metadata","display_name":"Value","name":"value","type":"str"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","value":[]},"ef_construction":{"_input_type":"IntInput","advanced":true,"display_name":"EF Construction","dynamic":false,"info":"Size of the dynamic candidate list during index construction. Higher values improve recall but increase indexing time and memory usage.","list":false,"list_add_label":"Add More","name":"ef_construction","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":512},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"engine":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Vector Engine","dynamic":false,"external_options":{},"info":"Vector search engine for similarity calculations. 'jvector' is recommended for most use cases. Note: Amazon OpenSearch Serverless only supports 'nmslib' or 'faiss'.","name":"engine","options":["jvector","nmslib","faiss","lucene"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"jvector"},"filter_expression":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Search Filters (JSON)","dynamic":false,"info":"Optional JSON configuration for search filtering, result limits, and score thresholds.\n\nFormat 1 - Explicit filters:\n{\"filter\": [{\"term\": {\"filename\":\"doc.pdf\"}}, {\"terms\":{\"owner\":[\"user1\",\"user2\"]}}], \"limit\": 10, \"score_threshold\": 1.6}\n\nFormat 2 - Context-style mapping:\n{\"data_sources\":[\"file.pdf\"], \"document_types\":[\"application/pdf\"], \"owners\":[\"user123\"]}\n\nUse __IMPOSSIBLE_VALUE__ as placeholder to ignore specific filters.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"filter_expression","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":""},"index_name":{"_input_type":"StrInput","advanced":false,"display_name":"Index Name","dynamic":false,"info":"The OpenSearch index name where documents will be stored and searched. Will be created automatically if it doesn't exist.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"index_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"langflow"},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"jwt_header":{"_input_type":"StrInput","advanced":true,"display_name":"JWT Header Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"jwt_header","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"Authorization"},"jwt_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"JWT Token","dynamic":false,"info":"Valid JSON Web Token for authentication. Will be sent in the Authorization header (with optional 'Bearer ' prefix).","input_types":[],"load_from_db":false,"name":"jwt_token","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"JWT"},"m":{"_input_type":"IntInput","advanced":true,"display_name":"M Parameter","dynamic":false,"info":"Number of bidirectional connections for each vector in the HNSW graph. Higher values improve search quality but increase memory usage and indexing time.","list":false,"list_add_label":"Add More","name":"m","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":16},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Default Result Limit","dynamic":false,"info":"Default maximum number of search results to return when no limit is specified in the filter expression.","list":false,"list_add_label":"Add More","name":"number_of_results","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":10},"opensearch_url":{"_input_type":"StrInput","advanced":false,"display_name":"OpenSearch URL","dynamic":false,"info":"The connection URL for your OpenSearch cluster (e.g., http://localhost:9200 for local development or your cloud endpoint).","list":false,"list_add_label":"Add More","load_from_db":false,"name":"opensearch_url","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"http://localhost:9200"},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"OpenSearch Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":"admin"},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"space_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Distance Metric","dynamic":false,"external_options":{},"info":"Distance metric for calculating vector similarity. 'l2' (Euclidean) is most common, 'cosinesimil' for cosine similarity, 'innerproduct' for dot product.","name":"space_type","options":["l2","l1","cosinesimil","linf","innerproduct"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"l2"},"use_ssl":{"_input_type":"BoolInput","advanced":true,"display_name":"Use SSL/TLS","dynamic":false,"info":"Enable SSL/TLS encryption for secure connections to OpenSearch.","list":false,"list_add_label":"Add More","name":"use_ssl","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"admin"},"vector_field":{"_input_type":"StrInput","advanced":true,"display_name":"Vector Field Name","dynamic":false,"info":"Name of the field in OpenSearch documents that stores the vector embeddings for similarity search.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"vector_field","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"chunk_embedding"},"verify_certs":{"_input_type":"BoolInput","advanced":true,"display_name":"Verify SSL Certificates","dynamic":false,"info":"Verify SSL certificates when connecting. Disable for self-signed certificates in development environments.","list":false,"list_add_label":"Add More","name":"verify_certs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"OpenSearchVectorStoreComponentMultimodalMultiEmbedding":{"base_classes":["Data","DataFrame","VectorStore"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Store and search documents using OpenSearch with multi-model hybrid semantic and keyword search.","display_name":"OpenSearch (Multi-Model Multi-Embedding)","documentation":"","edited":false,"field_order":["docs_metadata","opensearch_url","index_name","engine","space_type","ef_construction","m","num_candidates","ingest_data","search_query","should_cache_vector_store","embedding","embedding_model_name","vector_field","number_of_results","filter_expression","auth_mode","username","password","jwt_token","jwt_header","bearer_prefix","use_ssl","verify_certs"],"frozen":false,"icon":"OpenSearch","legacy":false,"metadata":{"code_hash":"a52b7daaae16","dependencies":{"dependencies":[{"name":"opensearchpy","version":"2.8.0"},{"name":"lfx","version":null},{"name":"tenacity","version":"8.5.0"}],"total_dependencies":3},"module":"lfx.components.elastic.opensearch_multimodal.OpenSearchVectorStoreComponentMultimodalMultiEmbedding"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Search Results","group_outputs":false,"method":"search_documents","name":"search_results","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"DataFrame","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"},{"allows_loop":false,"cache":true,"display_name":"Vector Store Connection","group_outputs":false,"hidden":false,"method":"as_vector_store","name":"vectorstoreconnection","selected":"VectorStore","tool_mode":true,"types":["VectorStore"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","auth_mode":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Authentication Mode","dynamic":false,"external_options":{},"info":"Authentication method: 'basic' for username/password authentication, or 'jwt' for JSON Web Token (Bearer) authentication.","name":"auth_mode","options":["basic","jwt"],"options_metadata":[],"override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"basic"},"bearer_prefix":{"_input_type":"BoolInput","advanced":true,"display_name":"Prefix 'Bearer '","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"bearer_prefix","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"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":"from __future__ import annotations\n\nimport copy\nimport json\nimport time\nimport uuid\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom typing import Any\n\nfrom opensearchpy import OpenSearch, helpers\nfrom opensearchpy.exceptions import OpenSearchException, RequestError\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.base.vectorstores.vector_store_connection_decorator import vector_store_connection\nfrom lfx.io import BoolInput, DropdownInput, HandleInput, IntInput, MultilineInput, SecretStrInput, StrInput, TableInput\nfrom lfx.log import logger\nfrom lfx.schema.data import Data\n\n\ndef normalize_model_name(model_name: str) -> str:\n \"\"\"Normalize embedding model name for use as field suffix.\n\n Converts model names to valid OpenSearch field names by replacing\n special characters and ensuring alphanumeric format.\n\n Args:\n model_name: Original embedding model name (e.g., \"text-embedding-3-small\")\n\n Returns:\n Normalized field suffix (e.g., \"text_embedding_3_small\")\n \"\"\"\n normalized = model_name.lower()\n # Replace common separators with underscores\n normalized = normalized.replace(\"-\", \"_\").replace(\":\", \"_\").replace(\"/\", \"_\").replace(\".\", \"_\")\n # Remove any non-alphanumeric characters except underscores\n normalized = \"\".join(c if c.isalnum() or c == \"_\" else \"_\" for c in normalized)\n # Remove duplicate underscores\n while \"__\" in normalized:\n normalized = normalized.replace(\"__\", \"_\")\n return normalized.strip(\"_\")\n\n\ndef get_embedding_field_name(model_name: str) -> str:\n \"\"\"Get the dynamic embedding field name for a model.\n\n Args:\n model_name: Embedding model name\n\n Returns:\n Field name in format: chunk_embedding_{normalized_model_name}\n \"\"\"\n logger.info(f\"chunk_embedding_{normalize_model_name(model_name)}\")\n return f\"chunk_embedding_{normalize_model_name(model_name)}\"\n\n\n@vector_store_connection\nclass OpenSearchVectorStoreComponentMultimodalMultiEmbedding(LCVectorStoreComponent):\n \"\"\"OpenSearch Vector Store Component with Multi-Model Hybrid Search Capabilities.\n\n This component provides vector storage and retrieval using OpenSearch, combining semantic\n similarity search (KNN) with keyword-based search for optimal results. It supports:\n - Multiple embedding models per index with dynamic field names\n - Automatic detection and querying of all available embedding models\n - Parallel embedding generation for multi-model search\n - Document ingestion with model tracking\n - Advanced filtering and aggregations\n - Flexible authentication options\n\n Features:\n - Multi-model vector storage with dynamic fields (chunk_embedding_{model_name})\n - Hybrid search combining multiple KNN queries (dis_max) + keyword matching\n - Auto-detection of available models in the index\n - Parallel query embedding generation for all detected models\n - Vector storage with configurable engines (jvector, nmslib, faiss, lucene)\n - Flexible authentication (Basic auth, JWT tokens)\n\n Model Name Resolution:\n - Priority: deployment > model > model_name attributes\n - This ensures correct matching between embedding objects and index fields\n - When multiple embeddings are provided, specify embedding_model_name to select which one to use\n - During search, each detected model in the index is matched to its corresponding embedding object\n \"\"\"\n\n display_name: str = \"OpenSearch (Multi-Model Multi-Embedding)\"\n icon: str = \"OpenSearch\"\n description: str = (\n \"Store and search documents using OpenSearch with multi-model hybrid semantic and keyword search.\"\n )\n\n # Keys we consider baseline\n default_keys: list[str] = [\n \"opensearch_url\",\n \"index_name\",\n *[i.name for i in LCVectorStoreComponent.inputs], # search_query, add_documents, etc.\n \"embedding\",\n \"embedding_model_name\",\n \"vector_field\",\n \"number_of_results\",\n \"auth_mode\",\n \"username\",\n \"password\",\n \"jwt_token\",\n \"jwt_header\",\n \"bearer_prefix\",\n \"use_ssl\",\n \"verify_certs\",\n \"filter_expression\",\n \"engine\",\n \"space_type\",\n \"ef_construction\",\n \"m\",\n \"num_candidates\",\n \"docs_metadata\",\n ]\n\n inputs = [\n TableInput(\n name=\"docs_metadata\",\n display_name=\"Document Metadata\",\n info=(\n \"Additional metadata key-value pairs to be added to all ingested documents. \"\n \"Useful for tagging documents with source information, categories, or other custom attributes.\"\n ),\n table_schema=[\n {\n \"name\": \"key\",\n \"display_name\": \"Key\",\n \"type\": \"str\",\n \"description\": \"Key name\",\n },\n {\n \"name\": \"value\",\n \"display_name\": \"Value\",\n \"type\": \"str\",\n \"description\": \"Value of the metadata\",\n },\n ],\n value=[],\n input_types=[\"Data\"],\n ),\n StrInput(\n name=\"opensearch_url\",\n display_name=\"OpenSearch URL\",\n value=\"http://localhost:9200\",\n info=(\n \"The connection URL for your OpenSearch cluster \"\n \"(e.g., http://localhost:9200 for local development or your cloud endpoint).\"\n ),\n ),\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow\",\n info=(\n \"The OpenSearch index name where documents will be stored and searched. \"\n \"Will be created automatically if it doesn't exist.\"\n ),\n ),\n DropdownInput(\n name=\"engine\",\n display_name=\"Vector Engine\",\n options=[\"jvector\", \"nmslib\", \"faiss\", \"lucene\"],\n value=\"jvector\",\n info=(\n \"Vector search engine for similarity calculations. 'jvector' is recommended for most use cases. \"\n \"Note: Amazon OpenSearch Serverless only supports 'nmslib' or 'faiss'.\"\n ),\n advanced=True,\n ),\n DropdownInput(\n name=\"space_type\",\n display_name=\"Distance Metric\",\n options=[\"l2\", \"l1\", \"cosinesimil\", \"linf\", \"innerproduct\"],\n value=\"l2\",\n info=(\n \"Distance metric for calculating vector similarity. 'l2' (Euclidean) is most common, \"\n \"'cosinesimil' for cosine similarity, 'innerproduct' for dot product.\"\n ),\n advanced=True,\n ),\n IntInput(\n name=\"ef_construction\",\n display_name=\"EF Construction\",\n value=512,\n info=(\n \"Size of the dynamic candidate list during index construction. \"\n \"Higher values improve recall but increase indexing time and memory usage.\"\n ),\n advanced=True,\n ),\n IntInput(\n name=\"m\",\n display_name=\"M Parameter\",\n value=16,\n info=(\n \"Number of bidirectional connections for each vector in the HNSW graph. \"\n \"Higher values improve search quality but increase memory usage and indexing time.\"\n ),\n advanced=True,\n ),\n IntInput(\n name=\"num_candidates\",\n display_name=\"Candidate Pool Size\",\n value=1000,\n info=(\n \"Number of approximate neighbors to consider for each KNN query. \"\n \"Some OpenSearch deployments do not support this parameter; set to 0 to disable.\"\n ),\n advanced=True,\n ),\n *LCVectorStoreComponent.inputs, # includes search_query, add_documents, etc.\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"], is_list=True),\n StrInput(\n name=\"embedding_model_name\",\n display_name=\"Embedding Model Name\",\n value=\"\",\n info=(\n \"Name of the embedding model to use for ingestion. This selects which embedding from the list \"\n \"will be used to embed documents. Matches on deployment, model, model_id, or model_name. \"\n \"For duplicate deployments, use combined format: 'deployment:model' \"\n \"(e.g., 'text-embedding-ada-002:text-embedding-3-large'). \"\n \"Leave empty to use the first embedding. Error message will show all available identifiers.\"\n ),\n advanced=False,\n ),\n StrInput(\n name=\"vector_field\",\n display_name=\"Legacy Vector Field Name\",\n value=\"chunk_embedding\",\n advanced=True,\n info=(\n \"Legacy field name for backward compatibility. New documents use dynamic fields \"\n \"(chunk_embedding_{model_name}) based on the embedding_model_name.\"\n ),\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Default Result Limit\",\n value=10,\n advanced=True,\n info=(\n \"Default maximum number of search results to return when no limit is \"\n \"specified in the filter expression.\"\n ),\n ),\n MultilineInput(\n name=\"filter_expression\",\n display_name=\"Search Filters (JSON)\",\n value=\"\",\n info=(\n \"Optional JSON configuration for search filtering, result limits, and score thresholds.\\n\\n\"\n \"Format 1 - Explicit filters:\\n\"\n '{\"filter\": [{\"term\": {\"filename\":\"doc.pdf\"}}, '\n '{\"terms\":{\"owner\":[\"user1\",\"user2\"]}}], \"limit\": 10, \"score_threshold\": 1.6}\\n\\n'\n \"Format 2 - Context-style mapping:\\n\"\n '{\"data_sources\":[\"file.pdf\"], \"document_types\":[\"application/pdf\"], \"owners\":[\"user123\"]}\\n\\n'\n \"Use __IMPOSSIBLE_VALUE__ as placeholder to ignore specific filters.\"\n ),\n ),\n # ----- Auth controls (dynamic) -----\n DropdownInput(\n name=\"auth_mode\",\n display_name=\"Authentication Mode\",\n value=\"basic\",\n options=[\"basic\", \"jwt\"],\n info=(\n \"Authentication method: 'basic' for username/password authentication, \"\n \"or 'jwt' for JSON Web Token (Bearer) authentication.\"\n ),\n real_time_refresh=True,\n advanced=False,\n ),\n StrInput(\n name=\"username\",\n display_name=\"Username\",\n value=\"admin\",\n show=True,\n ),\n SecretStrInput(\n name=\"password\",\n display_name=\"OpenSearch Password\",\n value=\"admin\",\n show=True,\n ),\n SecretStrInput(\n name=\"jwt_token\",\n display_name=\"JWT Token\",\n value=\"JWT\",\n load_from_db=False,\n show=False,\n info=(\n \"Valid JSON Web Token for authentication. \"\n \"Will be sent in the Authorization header (with optional 'Bearer ' prefix).\"\n ),\n ),\n StrInput(\n name=\"jwt_header\",\n display_name=\"JWT Header Name\",\n value=\"Authorization\",\n show=False,\n advanced=True,\n ),\n BoolInput(\n name=\"bearer_prefix\",\n display_name=\"Prefix 'Bearer '\",\n value=True,\n show=False,\n advanced=True,\n ),\n # ----- TLS -----\n BoolInput(\n name=\"use_ssl\",\n display_name=\"Use SSL/TLS\",\n value=True,\n advanced=True,\n info=\"Enable SSL/TLS encryption for secure connections to OpenSearch.\",\n ),\n BoolInput(\n name=\"verify_certs\",\n display_name=\"Verify SSL Certificates\",\n value=False,\n advanced=True,\n info=(\n \"Verify SSL certificates when connecting. \"\n \"Disable for self-signed certificates in development environments.\"\n ),\n ),\n ]\n\n def _get_embedding_model_name(self, embedding_obj=None) -> str:\n \"\"\"Get the embedding model name from component config or embedding object.\n\n Priority: deployment > model > model_id > model_name\n This ensures we use the actual model being deployed, not just the configured model.\n Supports multiple embedding providers (OpenAI, Watsonx, Cohere, etc.)\n\n Args:\n embedding_obj: Specific embedding object to get name from (optional)\n\n Returns:\n Embedding model name\n\n Raises:\n ValueError: If embedding model name cannot be determined\n \"\"\"\n # First try explicit embedding_model_name input\n if hasattr(self, \"embedding_model_name\") and self.embedding_model_name:\n return self.embedding_model_name.strip()\n\n # Try to get from provided embedding object\n if embedding_obj:\n # Priority: deployment > model > model_id > model_name\n if hasattr(embedding_obj, \"deployment\") and embedding_obj.deployment:\n return str(embedding_obj.deployment)\n if hasattr(embedding_obj, \"model\") and embedding_obj.model:\n return str(embedding_obj.model)\n if hasattr(embedding_obj, \"model_id\") and embedding_obj.model_id:\n return str(embedding_obj.model_id)\n if hasattr(embedding_obj, \"model_name\") and embedding_obj.model_name:\n return str(embedding_obj.model_name)\n\n # Try to get from embedding component (legacy single embedding)\n if hasattr(self, \"embedding\") and self.embedding:\n # Handle list of embeddings\n if isinstance(self.embedding, list) and len(self.embedding) > 0:\n first_emb = self.embedding[0]\n if hasattr(first_emb, \"deployment\") and first_emb.deployment:\n return str(first_emb.deployment)\n if hasattr(first_emb, \"model\") and first_emb.model:\n return str(first_emb.model)\n if hasattr(first_emb, \"model_id\") and first_emb.model_id:\n return str(first_emb.model_id)\n if hasattr(first_emb, \"model_name\") and first_emb.model_name:\n return str(first_emb.model_name)\n # Handle single embedding\n elif not isinstance(self.embedding, list):\n if hasattr(self.embedding, \"deployment\") and self.embedding.deployment:\n return str(self.embedding.deployment)\n if hasattr(self.embedding, \"model\") and self.embedding.model:\n return str(self.embedding.model)\n if hasattr(self.embedding, \"model_id\") and self.embedding.model_id:\n return str(self.embedding.model_id)\n if hasattr(self.embedding, \"model_name\") and self.embedding.model_name:\n return str(self.embedding.model_name)\n\n msg = (\n \"Could not determine embedding model name. \"\n \"Please set the 'embedding_model_name' field or ensure the embedding component \"\n \"has a 'deployment', 'model', 'model_id', or 'model_name' attribute.\"\n )\n raise ValueError(msg)\n\n # ---------- helper functions for index management ----------\n def _default_text_mapping(\n self,\n dim: int,\n engine: str = \"jvector\",\n space_type: str = \"l2\",\n ef_search: int = 512,\n ef_construction: int = 100,\n m: int = 16,\n vector_field: str = \"vector_field\",\n ) -> dict[str, Any]:\n \"\"\"Create the default OpenSearch index mapping for vector search.\n\n This method generates the index configuration with k-NN settings optimized\n for approximate nearest neighbor search using the specified vector engine.\n Includes the embedding_model keyword field for tracking which model was used.\n\n Args:\n dim: Dimensionality of the vector embeddings\n engine: Vector search engine (jvector, nmslib, faiss, lucene)\n space_type: Distance metric for similarity calculation\n ef_search: Size of dynamic list used during search\n ef_construction: Size of dynamic list used during index construction\n m: Number of bidirectional links for each vector\n vector_field: Name of the field storing vector embeddings\n\n Returns:\n Dictionary containing OpenSearch index mapping configuration\n \"\"\"\n return {\n \"settings\": {\"index\": {\"knn\": True, \"knn.algo_param.ef_search\": ef_search}},\n \"mappings\": {\n \"properties\": {\n vector_field: {\n \"type\": \"knn_vector\",\n \"dimension\": dim,\n \"method\": {\n \"name\": \"disk_ann\",\n \"space_type\": space_type,\n \"engine\": engine,\n \"parameters\": {\"ef_construction\": ef_construction, \"m\": m},\n },\n },\n \"embedding_model\": {\"type\": \"keyword\"}, # Track which model was used\n \"embedding_dimensions\": {\"type\": \"integer\"},\n }\n },\n }\n\n def _ensure_embedding_field_mapping(\n self,\n client: OpenSearch,\n index_name: str,\n field_name: str,\n dim: int,\n engine: str,\n space_type: str,\n ef_construction: int,\n m: int,\n ) -> None:\n \"\"\"Lazily add a dynamic embedding field to the index if it doesn't exist.\n\n This allows adding new embedding models without recreating the entire index.\n Also ensures the embedding_model tracking field exists.\n\n Args:\n client: OpenSearch client instance\n index_name: Target index name\n field_name: Dynamic field name for this embedding model\n dim: Vector dimensionality\n engine: Vector search engine\n space_type: Distance metric\n ef_construction: Construction parameter\n m: HNSW parameter\n \"\"\"\n try:\n mapping = {\n \"properties\": {\n field_name: {\n \"type\": \"knn_vector\",\n \"dimension\": dim,\n \"method\": {\n \"name\": \"disk_ann\",\n \"space_type\": space_type,\n \"engine\": engine,\n \"parameters\": {\"ef_construction\": ef_construction, \"m\": m},\n },\n },\n # Also ensure the embedding_model tracking field exists as keyword\n \"embedding_model\": {\"type\": \"keyword\"},\n \"embedding_dimensions\": {\"type\": \"integer\"},\n }\n }\n client.indices.put_mapping(index=index_name, body=mapping)\n logger.info(f\"Added/updated embedding field mapping: {field_name}\")\n except Exception as e:\n logger.warning(f\"Could not add embedding field mapping for {field_name}: {e}\")\n raise\n\n properties = self._get_index_properties(client)\n if not self._is_knn_vector_field(properties, field_name):\n msg = f\"Field '{field_name}' is not mapped as knn_vector. Current mapping: {properties.get(field_name)}\"\n logger.aerror(msg)\n raise ValueError(msg)\n\n def _validate_aoss_with_engines(self, *, is_aoss: bool, engine: str) -> None:\n \"\"\"Validate engine compatibility with Amazon OpenSearch Serverless (AOSS).\n\n Amazon OpenSearch Serverless has restrictions on which vector engines\n can be used. This method ensures the selected engine is compatible.\n\n Args:\n is_aoss: Whether the connection is to Amazon OpenSearch Serverless\n engine: The selected vector search engine\n\n Raises:\n ValueError: If AOSS is used with an incompatible engine\n \"\"\"\n if is_aoss and engine not in {\"nmslib\", \"faiss\"}:\n msg = \"Amazon OpenSearch Service Serverless only supports `nmslib` or `faiss` engines\"\n raise ValueError(msg)\n\n def _is_aoss_enabled(self, http_auth: Any) -> bool:\n \"\"\"Determine if Amazon OpenSearch Serverless (AOSS) is being used.\n\n Args:\n http_auth: The HTTP authentication object\n\n Returns:\n True if AOSS is enabled, False otherwise\n \"\"\"\n return http_auth is not None and hasattr(http_auth, \"service\") and http_auth.service == \"aoss\"\n\n def _bulk_ingest_embeddings(\n self,\n client: OpenSearch,\n index_name: str,\n embeddings: list[list[float]],\n texts: list[str],\n metadatas: list[dict] | None = None,\n ids: list[str] | None = None,\n vector_field: str = \"vector_field\",\n text_field: str = \"text\",\n embedding_model: str = \"unknown\",\n mapping: dict | None = None,\n max_chunk_bytes: int | None = 1 * 1024 * 1024,\n *,\n is_aoss: bool = False,\n ) -> list[str]:\n \"\"\"Efficiently ingest multiple documents with embeddings into OpenSearch.\n\n This method uses bulk operations to insert documents with their vector\n embeddings and metadata into the specified OpenSearch index. Each document\n is tagged with the embedding_model name for tracking.\n\n Args:\n client: OpenSearch client instance\n index_name: Target index for document storage\n embeddings: List of vector embeddings for each document\n texts: List of document texts\n metadatas: Optional metadata dictionaries for each document\n ids: Optional document IDs (UUIDs generated if not provided)\n vector_field: Field name for storing vector embeddings\n text_field: Field name for storing document text\n embedding_model: Name of the embedding model used\n mapping: Optional index mapping configuration\n max_chunk_bytes: Maximum size per bulk request chunk\n is_aoss: Whether using Amazon OpenSearch Serverless\n\n Returns:\n List of document IDs that were successfully ingested\n \"\"\"\n if not mapping:\n mapping = {}\n\n requests = []\n return_ids = []\n vector_dimensions = len(embeddings[0]) if embeddings else None\n\n for i, text in enumerate(texts):\n metadata = metadatas[i] if metadatas else {}\n if vector_dimensions is not None and \"embedding_dimensions\" not in metadata:\n metadata = {**metadata, \"embedding_dimensions\": vector_dimensions}\n _id = ids[i] if ids else str(uuid.uuid4())\n request = {\n \"_op_type\": \"index\",\n \"_index\": index_name,\n vector_field: embeddings[i],\n text_field: text,\n \"embedding_model\": embedding_model, # Track which model was used\n **metadata,\n }\n if is_aoss:\n request[\"id\"] = _id\n else:\n request[\"_id\"] = _id\n requests.append(request)\n return_ids.append(_id)\n if metadatas:\n self.log(f\"Sample metadata: {metadatas[0] if metadatas else {}}\")\n helpers.bulk(client, requests, max_chunk_bytes=max_chunk_bytes)\n return return_ids\n\n # ---------- auth / client ----------\n def _build_auth_kwargs(self) -> dict[str, Any]:\n \"\"\"Build authentication configuration for OpenSearch client.\n\n Constructs the appropriate authentication parameters based on the\n selected auth mode (basic username/password or JWT token).\n\n Returns:\n Dictionary containing authentication configuration\n\n Raises:\n ValueError: If required authentication parameters are missing\n \"\"\"\n mode = (self.auth_mode or \"basic\").strip().lower()\n if mode == \"jwt\":\n token = (self.jwt_token or \"\").strip()\n if not token:\n msg = \"Auth Mode is 'jwt' but no jwt_token was provided.\"\n raise ValueError(msg)\n header_name = (self.jwt_header or \"Authorization\").strip()\n header_value = f\"Bearer {token}\" if self.bearer_prefix else token\n return {\"headers\": {header_name: header_value}}\n user = (self.username or \"\").strip()\n pwd = (self.password or \"\").strip()\n if not user or not pwd:\n msg = \"Auth Mode is 'basic' but username/password are missing.\"\n raise ValueError(msg)\n return {\"http_auth\": (user, pwd)}\n\n def build_client(self) -> OpenSearch:\n \"\"\"Create and configure an OpenSearch client instance.\n\n Returns:\n Configured OpenSearch client ready for operations\n \"\"\"\n auth_kwargs = self._build_auth_kwargs()\n return OpenSearch(\n hosts=[self.opensearch_url],\n use_ssl=self.use_ssl,\n verify_certs=self.verify_certs,\n ssl_assert_hostname=False,\n ssl_show_warn=False,\n **auth_kwargs,\n )\n\n @check_cached_vector_store\n def build_vector_store(self) -> OpenSearch:\n # Return raw OpenSearch client as our \"vector store.\"\n client = self.build_client()\n\n # Check if we're in ingestion-only mode (no search query)\n has_search_query = bool((self.search_query or \"\").strip())\n if not has_search_query:\n logger.debug(\"Ingestion-only mode activated: search operations will be skipped\")\n logger.debug(\"Starting ingestion mode...\")\n\n logger.warning(f\"Embedding: {self.embedding}\")\n self._add_documents_to_vector_store(client=client)\n return client\n\n # ---------- ingest ----------\n def _add_documents_to_vector_store(self, client: OpenSearch) -> None:\n \"\"\"Process and ingest documents into the OpenSearch vector store.\n\n This method handles the complete document ingestion pipeline:\n - Prepares document data and metadata\n - Generates vector embeddings using the selected model\n - Creates appropriate index mappings with dynamic field names\n - Bulk inserts documents with vectors and model tracking\n\n Args:\n client: OpenSearch client for performing operations\n \"\"\"\n logger.debug(\"[INGESTION] _add_documents_to_vector_store called\")\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n logger.debug(\n f\"[INGESTION] ingest_data type: \"\n f\"{type(self.ingest_data)}, length: {len(self.ingest_data) if self.ingest_data else 0}\"\n )\n logger.debug(\n f\"[INGESTION] ingest_data content: \"\n f\"{self.ingest_data[:2] if self.ingest_data and len(self.ingest_data) > 0 else 'empty'}\"\n )\n\n docs = self.ingest_data or []\n if not docs:\n logger.debug(\"Ingestion complete: No documents provided\")\n return\n\n if not self.embedding:\n msg = \"Embedding handle is required to embed documents.\"\n raise ValueError(msg)\n\n # Normalize embedding to list first\n embeddings_list = self.embedding if isinstance(self.embedding, list) else [self.embedding]\n\n # Filter out None values (fail-safe mode) - do this BEFORE checking if empty\n embeddings_list = [e for e in embeddings_list if e is not None]\n\n # NOW check if we have any valid embeddings left after filtering\n if not embeddings_list:\n logger.warning(\"All embeddings returned None (fail-safe mode enabled). Skipping document ingestion.\")\n self.log(\"Embedding returned None (fail-safe mode enabled). Skipping document ingestion.\")\n return\n\n logger.debug(f\"[INGESTION] Valid embeddings after filtering: {len(embeddings_list)}\")\n self.log(f\"Available embedding models: {len(embeddings_list)}\")\n\n # Select the embedding to use for ingestion\n selected_embedding = None\n embedding_model = None\n\n # If embedding_model_name is specified, find matching embedding\n if hasattr(self, \"embedding_model_name\") and self.embedding_model_name and self.embedding_model_name.strip():\n target_model_name = self.embedding_model_name.strip()\n self.log(f\"Looking for embedding model: {target_model_name}\")\n\n for emb_obj in embeddings_list:\n # Check all possible model identifiers (deployment, model, model_id, model_name)\n # Also check available_models list from EmbeddingsWithModels\n possible_names = []\n deployment = getattr(emb_obj, \"deployment\", None)\n model = getattr(emb_obj, \"model\", None)\n model_id = getattr(emb_obj, \"model_id\", None)\n model_name = getattr(emb_obj, \"model_name\", None)\n available_models_attr = getattr(emb_obj, \"available_models\", None)\n\n if deployment:\n possible_names.append(str(deployment))\n if model:\n possible_names.append(str(model))\n if model_id:\n possible_names.append(str(model_id))\n if model_name:\n possible_names.append(str(model_name))\n\n # Also add combined identifier\n if deployment and model and deployment != model:\n possible_names.append(f\"{deployment}:{model}\")\n\n # Add all models from available_models dict\n if available_models_attr and isinstance(available_models_attr, dict):\n possible_names.extend(\n str(model_key).strip()\n for model_key in available_models_attr\n if model_key and str(model_key).strip()\n )\n\n # Match if target matches any of the possible names\n if target_model_name in possible_names:\n # Check if target is in available_models dict - use dedicated instance\n if (\n available_models_attr\n and isinstance(available_models_attr, dict)\n and target_model_name in available_models_attr\n ):\n # Use the dedicated embedding instance from the dict\n selected_embedding = available_models_attr[target_model_name]\n embedding_model = target_model_name\n self.log(f\"Found dedicated embedding instance for '{embedding_model}' in available_models dict\")\n else:\n # Traditional identifier match\n selected_embedding = emb_obj\n embedding_model = self._get_embedding_model_name(emb_obj)\n self.log(f\"Found matching embedding model: {embedding_model} (matched on: {target_model_name})\")\n break\n\n if not selected_embedding:\n # Build detailed list of available embeddings with all their identifiers\n available_info = []\n for idx, emb in enumerate(embeddings_list):\n emb_type = type(emb).__name__\n identifiers = []\n deployment = getattr(emb, \"deployment\", None)\n model = getattr(emb, \"model\", None)\n model_id = getattr(emb, \"model_id\", None)\n model_name = getattr(emb, \"model_name\", None)\n available_models_attr = getattr(emb, \"available_models\", None)\n\n if deployment:\n identifiers.append(f\"deployment='{deployment}'\")\n if model:\n identifiers.append(f\"model='{model}'\")\n if model_id:\n identifiers.append(f\"model_id='{model_id}'\")\n if model_name:\n identifiers.append(f\"model_name='{model_name}'\")\n\n # Add combined identifier as an option\n if deployment and model and deployment != model:\n identifiers.append(f\"combined='{deployment}:{model}'\")\n\n # Add available_models dict if present\n if available_models_attr and isinstance(available_models_attr, dict):\n identifiers.append(f\"available_models={list(available_models_attr.keys())}\")\n\n available_info.append(\n f\" [{idx}] {emb_type}: {', '.join(identifiers) if identifiers else 'No identifiers'}\"\n )\n\n msg = (\n f\"Embedding model '{target_model_name}' not found in available embeddings.\\n\\n\"\n f\"Available embeddings:\\n\" + \"\\n\".join(available_info) + \"\\n\\n\"\n \"Please set 'embedding_model_name' to one of the identifier values shown above \"\n \"(use the value after the '=' sign, without quotes).\\n\"\n \"For duplicate deployments, use the 'combined' format.\\n\"\n \"Or leave it empty to use the first embedding.\"\n )\n raise ValueError(msg)\n else:\n # Use first embedding if no model name specified\n selected_embedding = embeddings_list[0]\n embedding_model = self._get_embedding_model_name(selected_embedding)\n self.log(f\"No embedding_model_name specified, using first embedding: {embedding_model}\")\n\n dynamic_field_name = get_embedding_field_name(embedding_model)\n\n logger.info(f\"Selected embedding model for ingestion: '{embedding_model}'\")\n self.log(f\"Using embedding model for ingestion: {embedding_model}\")\n self.log(f\"Dynamic vector field: {dynamic_field_name}\")\n\n # Log embedding details for debugging\n if hasattr(selected_embedding, \"deployment\"):\n logger.info(f\"Embedding deployment: {selected_embedding.deployment}\")\n if hasattr(selected_embedding, \"model\"):\n logger.info(f\"Embedding model: {selected_embedding.model}\")\n if hasattr(selected_embedding, \"model_id\"):\n logger.info(f\"Embedding model_id: {selected_embedding.model_id}\")\n if hasattr(selected_embedding, \"dimensions\"):\n logger.info(f\"Embedding dimensions: {selected_embedding.dimensions}\")\n if hasattr(selected_embedding, \"available_models\"):\n logger.info(f\"Embedding available_models: {selected_embedding.available_models}\")\n\n # No model switching needed - each model in available_models has its own dedicated instance\n # The selected_embedding is already configured correctly for the target model\n logger.info(f\"Using embedding instance for '{embedding_model}' - pre-configured and ready to use\")\n\n # Extract texts and metadata from documents\n texts = []\n metadatas = []\n # Process docs_metadata table input into a dict\n additional_metadata = {}\n logger.debug(f\"[LF] Docs metadata {self.docs_metadata}\")\n if hasattr(self, \"docs_metadata\") and self.docs_metadata:\n logger.info(f\"[LF] Docs metadata {self.docs_metadata}\")\n if isinstance(self.docs_metadata[-1], Data):\n logger.info(f\"[LF] Docs metadata is a Data object {self.docs_metadata}\")\n self.docs_metadata = self.docs_metadata[-1].data\n logger.info(f\"[LF] Docs metadata is a Data object {self.docs_metadata}\")\n additional_metadata.update(self.docs_metadata)\n else:\n for item in self.docs_metadata:\n if isinstance(item, dict) and \"key\" in item and \"value\" in item:\n additional_metadata[item[\"key\"]] = item[\"value\"]\n # Replace string \"None\" values with actual None\n for key, value in additional_metadata.items():\n if value == \"None\":\n additional_metadata[key] = None\n logger.info(f\"[LF] Additional metadata {additional_metadata}\")\n for doc_obj in docs:\n data_copy = json.loads(doc_obj.model_dump_json())\n text = data_copy.pop(doc_obj.text_key, doc_obj.default_value)\n texts.append(text)\n\n # Merge additional metadata from table input\n data_copy.update(additional_metadata)\n\n metadatas.append(data_copy)\n self.log(metadatas)\n\n # Generate embeddings with rate-limit-aware retry logic using tenacity\n from tenacity import (\n retry,\n retry_if_exception,\n stop_after_attempt,\n wait_exponential,\n )\n\n def is_rate_limit_error(exception: Exception) -> bool:\n \"\"\"Check if exception is a rate limit error (429).\"\"\"\n error_str = str(exception).lower()\n return \"429\" in error_str or \"rate_limit\" in error_str or \"rate limit\" in error_str\n\n def is_other_retryable_error(exception: Exception) -> bool:\n \"\"\"Check if exception is retryable but not a rate limit error.\"\"\"\n # Retry on most exceptions except for specific non-retryable ones\n # Add other non-retryable exceptions here if needed\n return not is_rate_limit_error(exception)\n\n # Create retry decorator for rate limit errors (longer backoff)\n retry_on_rate_limit = retry(\n retry=retry_if_exception(is_rate_limit_error),\n stop=stop_after_attempt(5),\n wait=wait_exponential(multiplier=2, min=2, max=30),\n reraise=True,\n before_sleep=lambda retry_state: logger.warning(\n f\"Rate limit hit for chunk (attempt {retry_state.attempt_number}/5), \"\n f\"backing off for {retry_state.next_action.sleep:.1f}s\"\n ),\n )\n\n # Create retry decorator for other errors (shorter backoff)\n retry_on_other_errors = retry(\n retry=retry_if_exception(is_other_retryable_error),\n stop=stop_after_attempt(3),\n wait=wait_exponential(multiplier=1, min=1, max=8),\n reraise=True,\n before_sleep=lambda retry_state: logger.warning(\n f\"Error embedding chunk (attempt {retry_state.attempt_number}/3), \"\n f\"retrying in {retry_state.next_action.sleep:.1f}s: {retry_state.outcome.exception()}\"\n ),\n )\n\n def embed_chunk_with_retry(chunk_text: str, chunk_idx: int) -> list[float]:\n \"\"\"Embed a single chunk with rate-limit-aware retry logic.\"\"\"\n\n @retry_on_rate_limit\n @retry_on_other_errors\n def _embed(text: str) -> list[float]:\n return selected_embedding.embed_documents([text])[0]\n\n try:\n return _embed(chunk_text)\n except Exception as e:\n logger.error(\n f\"Failed to embed chunk {chunk_idx} after all retries: {e}\",\n error=str(e),\n )\n raise\n\n # Restrict concurrency for IBM/Watsonx models to avoid rate limits\n is_ibm = (embedding_model and \"ibm\" in str(embedding_model).lower()) or (\n selected_embedding and \"watsonx\" in type(selected_embedding).__name__.lower()\n )\n logger.debug(f\"Is IBM: {is_ibm}\")\n\n # For IBM models, use sequential processing with rate limiting\n # For other models, use parallel processing\n vectors: list[list[float]] = [None] * len(texts)\n\n if is_ibm:\n # Sequential processing with inter-request delay for IBM models\n inter_request_delay = 0.6 # ~1.67 req/s, safely under 2 req/s limit\n logger.info(f\"Using sequential processing for IBM model with {inter_request_delay}s delay between requests\")\n\n for idx, chunk in enumerate(texts):\n if idx > 0:\n # Add delay between requests (but not before the first one)\n time.sleep(inter_request_delay)\n vectors[idx] = embed_chunk_with_retry(chunk, idx)\n else:\n # Parallel processing for non-IBM models\n max_workers = min(max(len(texts), 1), 8)\n logger.debug(f\"Using parallel processing with {max_workers} workers\")\n\n with ThreadPoolExecutor(max_workers=max_workers) as executor:\n futures = {executor.submit(embed_chunk_with_retry, chunk, idx): idx for idx, chunk in enumerate(texts)}\n for future in as_completed(futures):\n idx = futures[future]\n vectors[idx] = future.result()\n\n if not vectors:\n self.log(f\"No vectors generated from documents for model {embedding_model}.\")\n return\n\n # Get vector dimension for mapping\n dim = len(vectors[0]) if vectors else 768 # default fallback\n\n # Check for AOSS\n auth_kwargs = self._build_auth_kwargs()\n is_aoss = self._is_aoss_enabled(auth_kwargs.get(\"http_auth\"))\n\n # Validate engine with AOSS\n engine = getattr(self, \"engine\", \"jvector\")\n self._validate_aoss_with_engines(is_aoss=is_aoss, engine=engine)\n\n # Create mapping with proper KNN settings\n space_type = getattr(self, \"space_type\", \"l2\")\n ef_construction = getattr(self, \"ef_construction\", 512)\n m = getattr(self, \"m\", 16)\n\n mapping = self._default_text_mapping(\n dim=dim,\n engine=engine,\n space_type=space_type,\n ef_construction=ef_construction,\n m=m,\n vector_field=dynamic_field_name, # Use dynamic field name\n )\n\n # Ensure index exists with baseline mapping\n try:\n if not client.indices.exists(index=self.index_name):\n self.log(f\"Creating index '{self.index_name}' with base mapping\")\n client.indices.create(index=self.index_name, body=mapping)\n except RequestError as creation_error:\n if creation_error.error != \"resource_already_exists_exception\":\n logger.warning(f\"Failed to create index '{self.index_name}': {creation_error}\")\n\n # Ensure the dynamic field exists in the index\n self._ensure_embedding_field_mapping(\n client=client,\n index_name=self.index_name,\n field_name=dynamic_field_name,\n dim=dim,\n engine=engine,\n space_type=space_type,\n ef_construction=ef_construction,\n m=m,\n )\n\n self.log(f\"Indexing {len(texts)} documents into '{self.index_name}' with model '{embedding_model}'...\")\n logger.info(f\"Will store embeddings in field: {dynamic_field_name}\")\n logger.info(f\"Will tag documents with embedding_model: {embedding_model}\")\n\n # Use the bulk ingestion with model tracking\n return_ids = self._bulk_ingest_embeddings(\n client=client,\n index_name=self.index_name,\n embeddings=vectors,\n texts=texts,\n metadatas=metadatas,\n vector_field=dynamic_field_name, # Use dynamic field name\n text_field=\"text\",\n embedding_model=embedding_model, # Track the model\n mapping=mapping,\n is_aoss=is_aoss,\n )\n self.log(metadatas)\n\n logger.info(\n f\"Ingestion complete: Successfully indexed {len(return_ids)} documents with model '{embedding_model}'\"\n )\n self.log(f\"Successfully indexed {len(return_ids)} documents with model {embedding_model}.\")\n\n # ---------- helpers for filters ----------\n def _is_placeholder_term(self, term_obj: dict) -> bool:\n # term_obj like {\"filename\": \"__IMPOSSIBLE_VALUE__\"}\n return any(v == \"__IMPOSSIBLE_VALUE__\" for v in term_obj.values())\n\n def _coerce_filter_clauses(self, filter_obj: dict | None) -> list[dict]:\n \"\"\"Convert filter expressions into OpenSearch-compatible filter clauses.\n\n This method accepts two filter formats and converts them to standardized\n OpenSearch query clauses:\n\n Format A - Explicit filters:\n {\"filter\": [{\"term\": {\"field\": \"value\"}}, {\"terms\": {\"field\": [\"val1\", \"val2\"]}}],\n \"limit\": 10, \"score_threshold\": 1.5}\n\n Format B - Context-style mapping:\n {\"data_sources\": [\"file1.pdf\"], \"document_types\": [\"pdf\"], \"owners\": [\"user1\"]}\n\n Args:\n filter_obj: Filter configuration dictionary or None\n\n Returns:\n List of OpenSearch filter clauses (term/terms objects)\n Placeholder values with \"__IMPOSSIBLE_VALUE__\" are ignored\n \"\"\"\n if not filter_obj:\n return []\n\n # If it is a string, try to parse it once\n if isinstance(filter_obj, str):\n try:\n filter_obj = json.loads(filter_obj)\n except json.JSONDecodeError:\n # Not valid JSON - treat as no filters\n return []\n\n # Case A: already an explicit list/dict under \"filter\"\n if \"filter\" in filter_obj:\n raw = filter_obj[\"filter\"]\n if isinstance(raw, dict):\n raw = [raw]\n explicit_clauses: list[dict] = []\n for f in raw or []:\n if \"term\" in f and isinstance(f[\"term\"], dict) and not self._is_placeholder_term(f[\"term\"]):\n explicit_clauses.append(f)\n elif \"terms\" in f and isinstance(f[\"terms\"], dict):\n field, vals = next(iter(f[\"terms\"].items()))\n if isinstance(vals, list) and len(vals) > 0:\n explicit_clauses.append(f)\n return explicit_clauses\n\n # Case B: convert context-style maps into clauses\n field_mapping = {\n \"data_sources\": \"filename\",\n \"document_types\": \"mimetype\",\n \"owners\": \"owner\",\n }\n context_clauses: list[dict] = []\n for k, values in filter_obj.items():\n if not isinstance(values, list):\n continue\n field = field_mapping.get(k, k)\n if len(values) == 0:\n # Match-nothing placeholder (kept to mirror your tool semantics)\n context_clauses.append({\"term\": {field: \"__IMPOSSIBLE_VALUE__\"}})\n elif len(values) == 1:\n if values[0] != \"__IMPOSSIBLE_VALUE__\":\n context_clauses.append({\"term\": {field: values[0]}})\n else:\n context_clauses.append({\"terms\": {field: values}})\n return context_clauses\n\n def _detect_available_models(self, client: OpenSearch, filter_clauses: list[dict] | None = None) -> list[str]:\n \"\"\"Detect which embedding models have documents in the index.\n\n Uses aggregation to find all unique embedding_model values, optionally\n filtered to only documents matching the user's filter criteria.\n\n Args:\n client: OpenSearch client instance\n filter_clauses: Optional filter clauses to scope model detection\n\n Returns:\n List of embedding model names found in the index\n \"\"\"\n try:\n agg_query = {\"size\": 0, \"aggs\": {\"embedding_models\": {\"terms\": {\"field\": \"embedding_model\", \"size\": 10}}}}\n\n # Apply filters to model detection if any exist\n if filter_clauses:\n agg_query[\"query\"] = {\"bool\": {\"filter\": filter_clauses}}\n\n logger.debug(f\"Model detection query: {agg_query}\")\n result = client.search(\n index=self.index_name,\n body=agg_query,\n params={\"terminate_after\": 0},\n )\n buckets = result.get(\"aggregations\", {}).get(\"embedding_models\", {}).get(\"buckets\", [])\n models = [b[\"key\"] for b in buckets if b[\"key\"]]\n\n # Log detailed bucket info for debugging\n logger.info(\n f\"Detected embedding models in corpus: {models}\"\n + (f\" (with {len(filter_clauses)} filters)\" if filter_clauses else \"\")\n )\n if not models:\n total_hits = result.get(\"hits\", {}).get(\"total\", {})\n total_count = total_hits.get(\"value\", 0) if isinstance(total_hits, dict) else total_hits\n logger.warning(\n f\"No embedding_model values found in index '{self.index_name}'. \"\n f\"Total docs in index: {total_count}. \"\n f\"This may indicate documents were indexed without the embedding_model field.\"\n )\n except (OpenSearchException, KeyError, ValueError) as e:\n logger.warning(f\"Failed to detect embedding models: {e}\")\n # Fallback to current model\n fallback_model = self._get_embedding_model_name()\n logger.info(f\"Using fallback model: {fallback_model}\")\n return [fallback_model]\n else:\n return models\n\n def _get_index_properties(self, client: OpenSearch) -> dict[str, Any] | None:\n \"\"\"Retrieve flattened mapping properties for the current index.\"\"\"\n try:\n mapping = client.indices.get_mapping(index=self.index_name)\n except OpenSearchException as e:\n logger.warning(\n f\"Failed to fetch mapping for index '{self.index_name}': {e}. Proceeding without mapping metadata.\"\n )\n return None\n\n properties: dict[str, Any] = {}\n for index_data in mapping.values():\n props = index_data.get(\"mappings\", {}).get(\"properties\", {})\n if isinstance(props, dict):\n properties.update(props)\n return properties\n\n def _is_knn_vector_field(self, properties: dict[str, Any] | None, field_name: str) -> bool:\n \"\"\"Check whether the field is mapped as a knn_vector.\"\"\"\n if not field_name:\n return False\n if properties is None:\n logger.warning(f\"Mapping metadata unavailable; assuming field '{field_name}' is usable.\")\n return True\n field_def = properties.get(field_name)\n if not isinstance(field_def, dict):\n return False\n if field_def.get(\"type\") == \"knn_vector\":\n return True\n\n nested_props = field_def.get(\"properties\")\n return bool(isinstance(nested_props, dict) and nested_props.get(\"type\") == \"knn_vector\")\n\n def _get_field_dimension(self, properties: dict[str, Any] | None, field_name: str) -> int | None:\n \"\"\"Get the dimension of a knn_vector field from the index mapping.\n\n Args:\n properties: Index properties from mapping\n field_name: Name of the vector field\n\n Returns:\n Dimension of the field, or None if not found\n \"\"\"\n if not field_name or properties is None:\n return None\n\n field_def = properties.get(field_name)\n if not isinstance(field_def, dict):\n return None\n\n # Check direct knn_vector field\n if field_def.get(\"type\") == \"knn_vector\":\n return field_def.get(\"dimension\")\n\n # Check nested properties\n nested_props = field_def.get(\"properties\")\n if isinstance(nested_props, dict) and nested_props.get(\"type\") == \"knn_vector\":\n return nested_props.get(\"dimension\")\n\n return None\n\n # ---------- search (multi-model hybrid) ----------\n def search(self, query: str | None = None) -> list[dict[str, Any]]:\n \"\"\"Perform multi-model hybrid search combining multiple vector similarities and keyword matching.\n\n This method executes a sophisticated search that:\n 1. Auto-detects all embedding models present in the index\n 2. Generates query embeddings for ALL detected models in parallel\n 3. Combines multiple KNN queries using dis_max (picks best match)\n 4. Adds keyword search with fuzzy matching (30% weight)\n 5. Applies optional filtering and score thresholds\n 6. Returns aggregations for faceted search\n\n Search weights:\n - Semantic search (dis_max across all models): 70%\n - Keyword search: 30%\n\n Args:\n query: Search query string (used for both vector embedding and keyword search)\n\n Returns:\n List of search results with page_content, metadata, and relevance scores\n\n Raises:\n ValueError: If embedding component is not provided or filter JSON is invalid\n \"\"\"\n logger.info(self.ingest_data)\n client = self.build_client()\n q = (query or \"\").strip()\n\n # Parse optional filter expression\n filter_obj = None\n if getattr(self, \"filter_expression\", \"\") and self.filter_expression.strip():\n try:\n filter_obj = json.loads(self.filter_expression)\n except json.JSONDecodeError as e:\n msg = f\"Invalid filter_expression JSON: {e}\"\n raise ValueError(msg) from e\n\n if not self.embedding:\n msg = \"Embedding is required to run hybrid search (KNN + keyword).\"\n raise ValueError(msg)\n\n # Check if embedding is None (fail-safe mode)\n if self.embedding is None or (isinstance(self.embedding, list) and all(e is None for e in self.embedding)):\n logger.error(\"Embedding returned None (fail-safe mode enabled). Cannot perform search.\")\n return []\n\n # Build filter clauses first so we can use them in model detection\n filter_clauses = self._coerce_filter_clauses(filter_obj)\n\n # Detect available embedding models in the index (scoped by filters)\n available_models = self._detect_available_models(client, filter_clauses)\n\n if not available_models:\n logger.warning(\"No embedding models found in index, using current model\")\n available_models = [self._get_embedding_model_name()]\n\n # Generate embeddings for ALL detected models\n query_embeddings = {}\n\n # Normalize embedding to list\n embeddings_list = self.embedding if isinstance(self.embedding, list) else [self.embedding]\n # Filter out None values (fail-safe mode)\n embeddings_list = [e for e in embeddings_list if e is not None]\n\n if not embeddings_list:\n logger.error(\n \"No valid embeddings available after filtering None values (fail-safe mode). Cannot perform search.\"\n )\n return []\n\n # Create a comprehensive map of model names to embedding objects\n # Check all possible identifiers (deployment, model, model_id, model_name)\n # Also leverage available_models list from EmbeddingsWithModels\n # Handle duplicate identifiers by creating combined keys\n embedding_by_model = {}\n identifier_conflicts = {} # Track which identifiers have conflicts\n\n for idx, emb_obj in enumerate(embeddings_list):\n # Get all possible identifiers for this embedding\n identifiers = []\n deployment = getattr(emb_obj, \"deployment\", None)\n model = getattr(emb_obj, \"model\", None)\n model_id = getattr(emb_obj, \"model_id\", None)\n model_name = getattr(emb_obj, \"model_name\", None)\n dimensions = getattr(emb_obj, \"dimensions\", None)\n available_models_attr = getattr(emb_obj, \"available_models\", None)\n\n logger.info(\n f\"Embedding object {idx}: deployment={deployment}, model={model}, \"\n f\"model_id={model_id}, model_name={model_name}, dimensions={dimensions}, \"\n f\"available_models={available_models_attr}\"\n )\n\n # If this embedding has available_models dict, map all models to their dedicated instances\n if available_models_attr and isinstance(available_models_attr, dict):\n logger.info(\n f\"Embedding object {idx} provides {len(available_models_attr)} models via available_models dict\"\n )\n for model_name_key, dedicated_embedding in available_models_attr.items():\n if model_name_key and str(model_name_key).strip():\n model_str = str(model_name_key).strip()\n if model_str not in embedding_by_model:\n # Use the dedicated embedding instance from the dict\n embedding_by_model[model_str] = dedicated_embedding\n logger.info(f\"Mapped available model '{model_str}' to dedicated embedding instance\")\n else:\n # Conflict detected - track it\n if model_str not in identifier_conflicts:\n identifier_conflicts[model_str] = [embedding_by_model[model_str]]\n identifier_conflicts[model_str].append(dedicated_embedding)\n logger.warning(f\"Available model '{model_str}' has conflict - used by multiple embeddings\")\n\n # Also map traditional identifiers (for backward compatibility)\n if deployment:\n identifiers.append(str(deployment))\n if model:\n identifiers.append(str(model))\n if model_id:\n identifiers.append(str(model_id))\n if model_name:\n identifiers.append(str(model_name))\n\n # Map all identifiers to this embedding object\n for identifier in identifiers:\n if identifier not in embedding_by_model:\n embedding_by_model[identifier] = emb_obj\n logger.info(f\"Mapped identifier '{identifier}' to embedding object {idx}\")\n else:\n # Conflict detected - track it\n if identifier not in identifier_conflicts:\n identifier_conflicts[identifier] = [embedding_by_model[identifier]]\n identifier_conflicts[identifier].append(emb_obj)\n logger.warning(f\"Identifier '{identifier}' has conflict - used by multiple embeddings\")\n\n # For embeddings with model+deployment, create combined identifier\n # This helps when deployment is the same but model differs\n if deployment and model and deployment != model:\n combined_id = f\"{deployment}:{model}\"\n if combined_id not in embedding_by_model:\n embedding_by_model[combined_id] = emb_obj\n logger.info(f\"Created combined identifier '{combined_id}' for embedding object {idx}\")\n\n # Log conflicts\n if identifier_conflicts:\n logger.warning(\n f\"Found {len(identifier_conflicts)} conflicting identifiers. \"\n f\"Consider using combined format 'deployment:model' or specifying unique model names.\"\n )\n for conflict_id, emb_list in identifier_conflicts.items():\n logger.warning(f\" Conflict on '{conflict_id}': {len(emb_list)} embeddings use this identifier\")\n\n logger.info(f\"Generating embeddings for {len(available_models)} models in index\")\n logger.info(f\"Available embedding identifiers: {list(embedding_by_model.keys())}\")\n self.log(f\"[SEARCH] Models detected in index: {available_models}\")\n self.log(f\"[SEARCH] Available embedding identifiers: {list(embedding_by_model.keys())}\")\n\n # Track matching status for debugging\n matched_models = []\n unmatched_models = []\n\n for model_name in available_models:\n try:\n # Check if we have an embedding object for this model\n if model_name in embedding_by_model:\n # Use the matching embedding object directly\n emb_obj = embedding_by_model[model_name]\n emb_deployment = getattr(emb_obj, \"deployment\", None)\n emb_model = getattr(emb_obj, \"model\", None)\n emb_model_id = getattr(emb_obj, \"model_id\", None)\n emb_dimensions = getattr(emb_obj, \"dimensions\", None)\n emb_available_models = getattr(emb_obj, \"available_models\", None)\n\n logger.info(\n f\"Using embedding object for model '{model_name}': \"\n f\"deployment={emb_deployment}, model={emb_model}, model_id={emb_model_id}, \"\n f\"dimensions={emb_dimensions}\"\n )\n\n # Check if this is a dedicated instance from available_models dict\n if emb_available_models and isinstance(emb_available_models, dict):\n logger.info(\n f\"Model '{model_name}' using dedicated instance from available_models dict \"\n f\"(pre-configured with correct model and dimensions)\"\n )\n\n # Use the embedding instance directly - no model switching needed!\n vec = emb_obj.embed_query(q)\n query_embeddings[model_name] = vec\n matched_models.append(model_name)\n logger.info(f\"Generated embedding for model: {model_name} (actual dimensions: {len(vec)})\")\n self.log(f\"[MATCH] Model '{model_name}' - generated {len(vec)}-dim embedding\")\n else:\n # No matching embedding found for this model\n unmatched_models.append(model_name)\n logger.warning(\n f\"No matching embedding found for model '{model_name}'. \"\n f\"This model will be skipped. Available identifiers: {list(embedding_by_model.keys())}\"\n )\n self.log(f\"[NO MATCH] Model '{model_name}' - available: {list(embedding_by_model.keys())}\")\n except (RuntimeError, ValueError, ConnectionError, TimeoutError, AttributeError, KeyError) as e:\n logger.warning(f\"Failed to generate embedding for {model_name}: {e}\")\n self.log(f\"[ERROR] Embedding generation failed for '{model_name}': {e}\")\n\n # Log summary of model matching\n logger.info(f\"Model matching summary: {len(matched_models)} matched, {len(unmatched_models)} unmatched\")\n self.log(f\"[SUMMARY] Model matching: {len(matched_models)} matched, {len(unmatched_models)} unmatched\")\n if unmatched_models:\n self.log(f\"[WARN] Unmatched models in index: {unmatched_models}\")\n\n if not query_embeddings:\n msg = (\n f\"Failed to generate embeddings for any model. \"\n f\"Index has models: {available_models}, but no matching embedding objects found. \"\n f\"Available embedding identifiers: {list(embedding_by_model.keys())}\"\n )\n self.log(f\"[FAIL] Search failed: {msg}\")\n raise ValueError(msg)\n\n index_properties = self._get_index_properties(client)\n legacy_vector_field = getattr(self, \"vector_field\", \"chunk_embedding\")\n\n # Build KNN queries for each model\n embedding_fields: list[str] = []\n knn_queries_with_candidates = []\n knn_queries_without_candidates = []\n\n raw_num_candidates = getattr(self, \"num_candidates\", 1000)\n try:\n num_candidates = int(raw_num_candidates) if raw_num_candidates is not None else 0\n except (TypeError, ValueError):\n num_candidates = 0\n use_num_candidates = num_candidates > 0\n\n for model_name, embedding_vector in query_embeddings.items():\n field_name = get_embedding_field_name(model_name)\n selected_field = field_name\n vector_dim = len(embedding_vector)\n\n # Only use the expected dynamic field - no legacy fallback\n # This prevents dimension mismatches between models\n if not self._is_knn_vector_field(index_properties, selected_field):\n logger.warning(\n f\"Skipping model {model_name}: field '{field_name}' is not mapped as knn_vector. \"\n f\"Documents must be indexed with this embedding model before querying.\"\n )\n self.log(f\"[SKIP] Field '{selected_field}' not a knn_vector - skipping model '{model_name}'\")\n continue\n\n # Validate vector dimensions match the field dimensions\n field_dim = self._get_field_dimension(index_properties, selected_field)\n if field_dim is not None and field_dim != vector_dim:\n logger.error(\n f\"Dimension mismatch for model '{model_name}': \"\n f\"Query vector has {vector_dim} dimensions but field '{selected_field}' expects {field_dim}. \"\n f\"Skipping this model to prevent search errors.\"\n )\n self.log(f\"[DIM MISMATCH] Model '{model_name}': query={vector_dim} vs field={field_dim} - skipping\")\n continue\n\n logger.info(\n f\"Adding KNN query for model '{model_name}': field='{selected_field}', \"\n f\"query_dims={vector_dim}, field_dims={field_dim or 'unknown'}\"\n )\n embedding_fields.append(selected_field)\n\n base_query = {\n \"knn\": {\n selected_field: {\n \"vector\": embedding_vector,\n \"k\": 50,\n }\n }\n }\n\n if use_num_candidates:\n query_with_candidates = copy.deepcopy(base_query)\n query_with_candidates[\"knn\"][selected_field][\"num_candidates\"] = num_candidates\n else:\n query_with_candidates = base_query\n\n knn_queries_with_candidates.append(query_with_candidates)\n knn_queries_without_candidates.append(base_query)\n\n if not knn_queries_with_candidates:\n # No valid fields found - this can happen when:\n # 1. Index is empty (no documents yet)\n # 2. Embedding model has changed and field doesn't exist yet\n # Return empty results instead of failing\n logger.warning(\n \"No valid knn_vector fields found for embedding models. \"\n \"This may indicate an empty index or missing field mappings. \"\n \"Returning empty search results.\"\n )\n self.log(\n f\"[WARN] No valid KNN queries could be built. \"\n f\"Query embeddings generated: {list(query_embeddings.keys())}, \"\n f\"but no matching knn_vector fields found in index.\"\n )\n return []\n\n # Build exists filter - document must have at least one embedding field\n exists_any_embedding = {\n \"bool\": {\"should\": [{\"exists\": {\"field\": f}} for f in set(embedding_fields)], \"minimum_should_match\": 1}\n }\n\n # Combine user filters with exists filter\n all_filters = [*filter_clauses, exists_any_embedding]\n\n # Get limit and score threshold\n limit = (filter_obj or {}).get(\"limit\", self.number_of_results)\n score_threshold = (filter_obj or {}).get(\"score_threshold\", 0)\n\n # Build multi-model hybrid query\n body = {\n \"query\": {\n \"bool\": {\n \"should\": [\n {\n \"dis_max\": {\n \"tie_breaker\": 0.0, # Take only the best match, no blending\n \"boost\": 0.7, # 70% weight for semantic search\n \"queries\": knn_queries_with_candidates,\n }\n },\n {\n \"multi_match\": {\n \"query\": q,\n \"fields\": [\"text^2\", \"filename^1.5\"],\n \"type\": \"best_fields\",\n \"fuzziness\": \"AUTO\",\n \"boost\": 0.3, # 30% weight for keyword search\n }\n },\n ],\n \"minimum_should_match\": 1,\n \"filter\": all_filters,\n }\n },\n \"aggs\": {\n \"data_sources\": {\"terms\": {\"field\": \"filename\", \"size\": 20}},\n \"document_types\": {\"terms\": {\"field\": \"mimetype\", \"size\": 10}},\n \"owners\": {\"terms\": {\"field\": \"owner\", \"size\": 10}},\n \"embedding_models\": {\"terms\": {\"field\": \"embedding_model\", \"size\": 10}},\n },\n \"_source\": [\n \"filename\",\n \"mimetype\",\n \"page\",\n \"text\",\n \"source_url\",\n \"owner\",\n \"embedding_model\",\n \"allowed_users\",\n \"allowed_groups\",\n ],\n \"size\": limit,\n }\n\n if isinstance(score_threshold, (int, float)) and score_threshold > 0:\n body[\"min_score\"] = score_threshold\n\n logger.info(\n f\"Executing multi-model hybrid search with {len(knn_queries_with_candidates)} embedding models: \"\n f\"{list(query_embeddings.keys())}\"\n )\n self.log(f\"[EXEC] Executing search with {len(knn_queries_with_candidates)} KNN queries, limit={limit}\")\n self.log(f\"[EXEC] Embedding models used: {list(query_embeddings.keys())}\")\n self.log(f\"[EXEC] KNN fields being queried: {embedding_fields}\")\n\n try:\n resp = client.search(index=self.index_name, body=body, params={\"terminate_after\": 0})\n except RequestError as e:\n error_message = str(e)\n lowered = error_message.lower()\n if use_num_candidates and \"num_candidates\" in lowered:\n logger.warning(\n \"Retrying search without num_candidates parameter due to cluster capabilities\",\n error=error_message,\n )\n fallback_body = copy.deepcopy(body)\n try:\n fallback_body[\"query\"][\"bool\"][\"should\"][0][\"dis_max\"][\"queries\"] = knn_queries_without_candidates\n except (KeyError, IndexError, TypeError) as inner_err:\n raise e from inner_err\n resp = client.search(\n index=self.index_name,\n body=fallback_body,\n params={\"terminate_after\": 0},\n )\n elif \"knn_vector\" in lowered or (\"field\" in lowered and \"knn\" in lowered):\n fallback_vector = next(iter(query_embeddings.values()), None)\n if fallback_vector is None:\n raise\n fallback_field = legacy_vector_field or \"chunk_embedding\"\n logger.warning(\n \"KNN search failed for dynamic fields; falling back to legacy field '%s'.\",\n fallback_field,\n )\n fallback_body = copy.deepcopy(body)\n fallback_body[\"query\"][\"bool\"][\"filter\"] = filter_clauses\n knn_fallback = {\n \"knn\": {\n fallback_field: {\n \"vector\": fallback_vector,\n \"k\": 50,\n }\n }\n }\n if use_num_candidates:\n knn_fallback[\"knn\"][fallback_field][\"num_candidates\"] = num_candidates\n fallback_body[\"query\"][\"bool\"][\"should\"][0][\"dis_max\"][\"queries\"] = [knn_fallback]\n resp = client.search(\n index=self.index_name,\n body=fallback_body,\n params={\"terminate_after\": 0},\n )\n else:\n raise\n hits = resp.get(\"hits\", {}).get(\"hits\", [])\n\n logger.info(f\"Found {len(hits)} results\")\n self.log(f\"[RESULT] Search complete: {len(hits)} results found\")\n\n if len(hits) == 0:\n self.log(\n f\"[EMPTY] Debug info: \"\n f\"models_in_index={available_models}, \"\n f\"matched_models={matched_models}, \"\n f\"knn_fields={embedding_fields}, \"\n f\"filters={len(filter_clauses)} clauses\"\n )\n\n return [\n {\n \"page_content\": hit[\"_source\"].get(\"text\", \"\"),\n \"metadata\": {k: v for k, v in hit[\"_source\"].items() if k != \"text\"},\n \"score\": hit.get(\"_score\"),\n }\n for hit in hits\n ]\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search documents and return results as Data objects.\n\n This is the main interface method that performs the multi-model search using the\n configured search_query and returns results in Langflow's Data format.\n\n Always builds the vector store (triggering ingestion if needed), then performs\n search only if a query is provided.\n\n Returns:\n List of Data objects containing search results with text and metadata\n\n Raises:\n Exception: If search operation fails\n \"\"\"\n try:\n # Always build/cache the vector store to ensure ingestion happens\n logger.info(f\"Search query: {self.search_query}\")\n if self._cached_vector_store is None:\n self.build_vector_store()\n\n # Only perform search if query is provided\n search_query = (self.search_query or \"\").strip()\n if not search_query:\n self.log(\"No search query provided - ingestion completed, returning empty results\")\n return []\n\n # Perform search with the provided query\n raw = self.search(search_query)\n return [Data(text=hit[\"page_content\"], **hit[\"metadata\"]) for hit in raw]\n except Exception as e:\n self.log(f\"search_documents error: {e}\")\n raise\n\n # -------- dynamic UI handling (auth switch) --------\n async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:\n \"\"\"Dynamically update component configuration based on field changes.\n\n This method handles real-time UI updates, particularly for authentication\n mode changes that show/hide relevant input fields.\n\n Args:\n build_config: Current component configuration\n field_value: New value for the changed field\n field_name: Name of the field that changed\n\n Returns:\n Updated build configuration with appropriate field visibility\n \"\"\"\n try:\n if field_name == \"auth_mode\":\n mode = (field_value or \"basic\").strip().lower()\n is_basic = mode == \"basic\"\n is_jwt = mode == \"jwt\"\n\n build_config[\"username\"][\"show\"] = is_basic\n build_config[\"password\"][\"show\"] = is_basic\n\n build_config[\"jwt_token\"][\"show\"] = is_jwt\n build_config[\"jwt_header\"][\"show\"] = is_jwt\n build_config[\"bearer_prefix\"][\"show\"] = is_jwt\n\n build_config[\"username\"][\"required\"] = is_basic\n build_config[\"password\"][\"required\"] = is_basic\n\n build_config[\"jwt_token\"][\"required\"] = is_jwt\n build_config[\"jwt_header\"][\"required\"] = is_jwt\n build_config[\"bearer_prefix\"][\"required\"] = False\n\n return build_config\n\n except (KeyError, ValueError) as e:\n self.log(f\"update_build_config error: {e}\")\n\n return build_config\n"},"docs_metadata":{"_input_type":"TableInput","advanced":false,"display_name":"Document Metadata","dynamic":false,"info":"Additional metadata key-value pairs to be added to all ingested documents. Useful for tagging documents with source information, categories, or other custom attributes.","input_types":["Data"],"is_list":true,"list_add_label":"Add More","name":"docs_metadata","override_skip":false,"placeholder":"","required":false,"show":true,"table_icon":"Table","table_schema":[{"description":"Key name","display_name":"Key","name":"key","type":"str"},{"description":"Value of the metadata","display_name":"Value","name":"value","type":"str"}],"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"trigger_icon":"Table","trigger_text":"Open table","type":"table","value":[]},"ef_construction":{"_input_type":"IntInput","advanced":true,"display_name":"EF Construction","dynamic":false,"info":"Size of the dynamic candidate list during index construction. Higher values improve recall but increase indexing time and memory usage.","list":false,"list_add_label":"Add More","name":"ef_construction","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":512},"embedding":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding","dynamic":false,"info":"","input_types":["Embeddings"],"list":true,"list_add_label":"Add More","name":"embedding","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"embedding_model_name":{"_input_type":"StrInput","advanced":false,"display_name":"Embedding Model Name","dynamic":false,"info":"Name of the embedding model to use for ingestion. This selects which embedding from the list will be used to embed documents. Matches on deployment, model, model_id, or model_name. For duplicate deployments, use combined format: 'deployment:model' (e.g., 'text-embedding-ada-002:text-embedding-3-large'). Leave empty to use the first embedding. Error message will show all available identifiers.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"embedding_model_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""},"engine":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Vector Engine","dynamic":false,"external_options":{},"info":"Vector search engine for similarity calculations. 'jvector' is recommended for most use cases. Note: Amazon OpenSearch Serverless only supports 'nmslib' or 'faiss'.","name":"engine","options":["jvector","nmslib","faiss","lucene"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"jvector"},"filter_expression":{"_input_type":"MultilineInput","advanced":false,"ai_enabled":false,"copy_field":false,"display_name":"Search Filters (JSON)","dynamic":false,"info":"Optional JSON configuration for search filtering, result limits, and score thresholds.\n\nFormat 1 - Explicit filters:\n{\"filter\": [{\"term\": {\"filename\":\"doc.pdf\"}}, {\"terms\":{\"owner\":[\"user1\",\"user2\"]}}], \"limit\": 10, \"score_threshold\": 1.6}\n\nFormat 2 - Context-style mapping:\n{\"data_sources\":[\"file.pdf\"], \"document_types\":[\"application/pdf\"], \"owners\":[\"user123\"]}\n\nUse __IMPOSSIBLE_VALUE__ as placeholder to ignore specific filters.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"multiline":true,"name":"filter_expression","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":""},"index_name":{"_input_type":"StrInput","advanced":false,"display_name":"Index Name","dynamic":false,"info":"The OpenSearch index name where documents will be stored and searched. Will be created automatically if it doesn't exist.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"index_name","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"langflow"},"ingest_data":{"_input_type":"HandleInput","advanced":false,"display_name":"Ingest Data","dynamic":false,"info":"","input_types":["Data","DataFrame"],"list":true,"list_add_label":"Add More","name":"ingest_data","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"jwt_header":{"_input_type":"StrInput","advanced":true,"display_name":"JWT Header Name","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"jwt_header","override_skip":false,"placeholder":"","required":false,"show":false,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"Authorization"},"jwt_token":{"_input_type":"SecretStrInput","advanced":false,"display_name":"JWT Token","dynamic":false,"info":"Valid JSON Web Token for authentication. Will be sent in the Authorization header (with optional 'Bearer ' prefix).","input_types":[],"load_from_db":false,"name":"jwt_token","override_skip":false,"password":true,"placeholder":"","required":false,"show":false,"title_case":false,"track_in_telemetry":false,"type":"str","value":"JWT"},"m":{"_input_type":"IntInput","advanced":true,"display_name":"M Parameter","dynamic":false,"info":"Number of bidirectional connections for each vector in the HNSW graph. Higher values improve search quality but increase memory usage and indexing time.","list":false,"list_add_label":"Add More","name":"m","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":16},"num_candidates":{"_input_type":"IntInput","advanced":true,"display_name":"Candidate Pool Size","dynamic":false,"info":"Number of approximate neighbors to consider for each KNN query. Some OpenSearch deployments do not support this parameter; set to 0 to disable.","list":false,"list_add_label":"Add More","name":"num_candidates","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":1000},"number_of_results":{"_input_type":"IntInput","advanced":true,"display_name":"Default Result Limit","dynamic":false,"info":"Default maximum number of search results to return when no limit is specified in the filter expression.","list":false,"list_add_label":"Add More","name":"number_of_results","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":10},"opensearch_url":{"_input_type":"StrInput","advanced":false,"display_name":"OpenSearch URL","dynamic":false,"info":"The connection URL for your OpenSearch cluster (e.g., http://localhost:9200 for local development or your cloud endpoint).","list":false,"list_add_label":"Add More","load_from_db":false,"name":"opensearch_url","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"http://localhost:9200"},"password":{"_input_type":"SecretStrInput","advanced":false,"display_name":"OpenSearch Password","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"password","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":"admin"},"search_query":{"_input_type":"QueryInput","advanced":false,"display_name":"Search Query","dynamic":false,"info":"Enter a query to run a similarity search.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"search_query","override_skip":false,"placeholder":"Enter a query...","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"query","value":""},"should_cache_vector_store":{"_input_type":"BoolInput","advanced":true,"display_name":"Cache Vector Store","dynamic":false,"info":"If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.","list":false,"list_add_label":"Add More","name":"should_cache_vector_store","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"space_type":{"_input_type":"DropdownInput","advanced":true,"combobox":false,"dialog_inputs":{},"display_name":"Distance Metric","dynamic":false,"external_options":{},"info":"Distance metric for calculating vector similarity. 'l2' (Euclidean) is most common, 'cosinesimil' for cosine similarity, 'innerproduct' for dot product.","name":"space_type","options":["l2","l1","cosinesimil","linf","innerproduct"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"l2"},"use_ssl":{"_input_type":"BoolInput","advanced":true,"display_name":"Use SSL/TLS","dynamic":false,"info":"Enable SSL/TLS encryption for secure connections to OpenSearch.","list":false,"list_add_label":"Add More","name":"use_ssl","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true},"username":{"_input_type":"StrInput","advanced":false,"display_name":"Username","dynamic":false,"info":"","list":false,"list_add_label":"Add More","load_from_db":false,"name":"username","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"admin"},"vector_field":{"_input_type":"StrInput","advanced":true,"display_name":"Legacy Vector Field Name","dynamic":false,"info":"Legacy field name for backward compatibility. New documents use dynamic fields (chunk_embedding_{model_name}) based on the embedding_model_name.","list":false,"list_add_label":"Add More","load_from_db":false,"name":"vector_field","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"chunk_embedding"},"verify_certs":{"_input_type":"BoolInput","advanced":true,"display_name":"Verify SSL Certificates","dynamic":false,"info":"Verify SSL certificates when connecting. Disable for self-signed certificates in development environments.","list":false,"list_add_label":"Add More","name":"verify_certs","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false}}],["embeddings",{"EmbeddingSimilarityComponent":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Compute selected form of similarity between two embedding vectors.","display_name":"Embedding Similarity","documentation":"","edited":false,"field_order":["embedding_vectors","similarity_metric"],"frozen":false,"icon":"equal","legacy":true,"metadata":{"code_hash":"d94c7d791f69","dependencies":{"dependencies":[{"name":"numpy","version":"2.2.6"},{"name":"lfx","version":null}],"total_dependencies":2},"module":"lfx.components.embeddings.similarity.EmbeddingSimilarityComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Similarity Data","group_outputs":false,"method":"compute_similarity","name":"similarity_data","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":["datastax.AstraDB"],"template":{"_type":"Component","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":"from typing import Any\n\nimport numpy as np\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import DataInput, DropdownInput, Output\nfrom lfx.schema.data import Data\n\n\nclass EmbeddingSimilarityComponent(Component):\n display_name: str = \"Embedding Similarity\"\n description: str = \"Compute selected form of similarity between two embedding vectors.\"\n icon = \"equal\"\n legacy: bool = True\n replacement = [\"datastax.AstraDB\"]\n\n inputs = [\n DataInput(\n name=\"embedding_vectors\",\n display_name=\"Embedding Vectors\",\n info=\"A list containing exactly two data objects with embedding vectors to compare.\",\n is_list=True,\n required=True,\n ),\n DropdownInput(\n name=\"similarity_metric\",\n display_name=\"Similarity Metric\",\n info=\"Select the similarity metric to use.\",\n options=[\"Cosine Similarity\", \"Euclidean Distance\", \"Manhattan Distance\"],\n value=\"Cosine Similarity\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Similarity Data\", name=\"similarity_data\", method=\"compute_similarity\"),\n ]\n\n def compute_similarity(self) -> Data:\n embedding_vectors: list[Data] = self.embedding_vectors\n\n # Assert that the list contains exactly two Data objects\n if len(embedding_vectors) != 2: # noqa: PLR2004\n msg = \"Exactly two embedding vectors are required.\"\n raise ValueError(msg)\n\n embedding_1 = np.array(embedding_vectors[0].data[\"embeddings\"])\n embedding_2 = np.array(embedding_vectors[1].data[\"embeddings\"])\n\n if embedding_1.shape != embedding_2.shape:\n similarity_score: dict[str, Any] = {\"error\": \"Embeddings must have the same dimensions.\"}\n else:\n similarity_metric = self.similarity_metric\n\n if similarity_metric == \"Cosine Similarity\":\n score = np.dot(embedding_1, embedding_2) / (np.linalg.norm(embedding_1) * np.linalg.norm(embedding_2))\n similarity_score = {\"cosine_similarity\": score}\n\n elif similarity_metric == \"Euclidean Distance\":\n score = np.linalg.norm(embedding_1 - embedding_2)\n similarity_score = {\"euclidean_distance\": score}\n\n elif similarity_metric == \"Manhattan Distance\":\n score = np.sum(np.abs(embedding_1 - embedding_2))\n similarity_score = {\"manhattan_distance\": score}\n\n # Create a Data object to encapsulate the similarity score and additional information\n similarity_data = Data(\n data={\n \"embedding_1\": embedding_vectors[0].data[\"embeddings\"],\n \"embedding_2\": embedding_vectors[1].data[\"embeddings\"],\n \"similarity_score\": similarity_score,\n },\n text_key=\"similarity_score\",\n )\n\n self.status = similarity_data\n return similarity_data\n"},"embedding_vectors":{"_input_type":"DataInput","advanced":false,"display_name":"Embedding Vectors","dynamic":false,"info":"A list containing exactly two data objects with embedding vectors to compare.","input_types":["Data"],"list":true,"list_add_label":"Add More","name":"embedding_vectors","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"similarity_metric":{"_input_type":"DropdownInput","advanced":false,"combobox":false,"dialog_inputs":{},"display_name":"Similarity Metric","dynamic":false,"external_options":{},"info":"Select the similarity metric to use.","name":"similarity_metric","options":["Cosine Similarity","Euclidean Distance","Manhattan Distance"],"options_metadata":[],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"str","value":"Cosine Similarity"}},"tool_mode":false},"TextEmbedderComponent":{"base_classes":["Data"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Generate embeddings for a given message using the specified embedding model.","display_name":"Text Embedder","documentation":"","edited":false,"field_order":["embedding_model","message"],"frozen":false,"icon":"binary","legacy":true,"metadata":{"code_hash":"541a2fb78066","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.embeddings.text_embedder.TextEmbedderComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Embedding Data","group_outputs":false,"method":"generate_embeddings","name":"embeddings","selected":"Data","tool_mode":true,"types":["Data"],"value":"__UNDEFINED__"}],"pinned":false,"replacement":["models.EmbeddingModel"],"template":{"_type":"Component","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":"from typing import TYPE_CHECKING\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import HandleInput, MessageInput, Output\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\nif TYPE_CHECKING:\n from lfx.field_typing import Embeddings\n from lfx.schema.message import Message\n\n\nclass TextEmbedderComponent(Component):\n display_name: str = \"Text Embedder\"\n description: str = \"Generate embeddings for a given message using the specified embedding model.\"\n icon = \"binary\"\n legacy: bool = True\n replacement = [\"models.EmbeddingModel\"]\n inputs = [\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n info=\"The embedding model to use for generating embeddings.\",\n input_types=[\"Embeddings\"],\n required=True,\n ),\n MessageInput(\n name=\"message\",\n display_name=\"Message\",\n info=\"The message to generate embeddings for.\",\n required=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Embedding Data\", name=\"embeddings\", method=\"generate_embeddings\"),\n ]\n\n def generate_embeddings(self) -> Data:\n try:\n embedding_model: Embeddings = self.embedding_model\n message: Message = self.message\n\n # Combine validation checks to reduce nesting\n if not embedding_model or not hasattr(embedding_model, \"embed_documents\"):\n msg = \"Invalid or incompatible embedding model\"\n raise ValueError(msg)\n\n text_content = message.text if message and message.text else \"\"\n if not text_content:\n msg = \"No text content found in message\"\n raise ValueError(msg)\n\n embeddings = embedding_model.embed_documents([text_content])\n if not embeddings or not isinstance(embeddings, list):\n msg = \"Invalid embeddings generated\"\n raise ValueError(msg)\n\n embedding_vector = embeddings[0]\n self.status = {\"text\": text_content, \"embeddings\": embedding_vector}\n return Data(data={\"text\": text_content, \"embeddings\": embedding_vector})\n except Exception as e: # noqa: BLE001\n logger.exception(\"Error generating embeddings\")\n error_data = Data(data={\"text\": \"\", \"embeddings\": [], \"error\": str(e)})\n self.status = {\"error\": str(e)}\n return error_data\n"},"embedding_model":{"_input_type":"HandleInput","advanced":false,"display_name":"Embedding Model","dynamic":false,"info":"The embedding model to use for generating embeddings.","input_types":["Embeddings"],"list":false,"list_add_label":"Add More","name":"embedding_model","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"other","value":""},"message":{"_input_type":"MessageInput","advanced":false,"display_name":"Message","dynamic":false,"info":"The message to generate embeddings for.","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"message","override_skip":false,"placeholder":"","required":true,"show":true,"title_case":false,"tool_mode":false,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":""}},"tool_mode":false}}],["exa",{"ExaSearch":{"base_classes":["Tool"],"beta":true,"conditional_paths":[],"custom_fields":{},"description":"Exa Search toolkit for search and content retrieval","display_name":"Exa Search","documentation":"https://python.langchain.com/docs/integrations/tools/metaphor_search","edited":false,"field_order":["metaphor_api_key","use_autoprompt","search_num_results","similar_num_results"],"frozen":false,"icon":"ExaSearch","legacy":false,"metadata":{"code_hash":"26039e2a8b78","dependencies":{"dependencies":[{"name":"langchain_core","version":"0.3.80"},{"name":"metaphor_python","version":"0.1.23"},{"name":"lfx","version":null}],"total_dependencies":3},"module":"lfx.components.exa.exa_search.ExaSearchToolkit"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Tools","group_outputs":false,"method":"build_toolkit","name":"tools","selected":"Tool","tool_mode":true,"types":["Tool"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from langchain_core.tools import tool\nfrom metaphor_python import Metaphor\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.field_typing import Tool\nfrom lfx.io import BoolInput, IntInput, Output, SecretStrInput\n\n\nclass ExaSearchToolkit(Component):\n display_name = \"Exa Search\"\n description = \"Exa Search toolkit for search and content retrieval\"\n documentation = \"https://python.langchain.com/docs/integrations/tools/metaphor_search\"\n beta = True\n name = \"ExaSearch\"\n icon = \"ExaSearch\"\n\n inputs = [\n SecretStrInput(\n name=\"metaphor_api_key\",\n display_name=\"Exa Search API Key\",\n password=True,\n ),\n BoolInput(\n name=\"use_autoprompt\",\n display_name=\"Use Autoprompt\",\n value=True,\n ),\n IntInput(\n name=\"search_num_results\",\n display_name=\"Search Number of Results\",\n value=5,\n ),\n IntInput(\n name=\"similar_num_results\",\n display_name=\"Similar Number of Results\",\n value=5,\n ),\n ]\n\n outputs = [\n Output(name=\"tools\", display_name=\"Tools\", method=\"build_toolkit\"),\n ]\n\n def build_toolkit(self) -> Tool:\n client = Metaphor(api_key=self.metaphor_api_key)\n\n @tool\n def search(query: str):\n \"\"\"Call search engine with a query.\"\"\"\n return client.search(query, use_autoprompt=self.use_autoprompt, num_results=self.search_num_results)\n\n @tool\n def get_contents(ids: list[str]):\n \"\"\"Get contents of a webpage.\n\n The ids passed in should be a list of ids as fetched from `search`.\n \"\"\"\n return client.get_contents(ids)\n\n @tool\n def find_similar(url: str):\n \"\"\"Get search results similar to a given URL.\n\n The url passed in should be a URL returned from `search`\n \"\"\"\n return client.find_similar(url, num_results=self.similar_num_results)\n\n return [search, get_contents, find_similar]\n"},"metaphor_api_key":{"_input_type":"SecretStrInput","advanced":false,"display_name":"Exa Search API Key","dynamic":false,"info":"","input_types":[],"load_from_db":true,"name":"metaphor_api_key","override_skip":false,"password":true,"placeholder":"","required":false,"show":true,"title_case":false,"track_in_telemetry":false,"type":"str","value":""},"search_num_results":{"_input_type":"IntInput","advanced":false,"display_name":"Search Number of Results","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"search_num_results","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":5},"similar_num_results":{"_input_type":"IntInput","advanced":false,"display_name":"Similar Number of Results","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"similar_num_results","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":5},"use_autoprompt":{"_input_type":"BoolInput","advanced":false,"display_name":"Use Autoprompt","dynamic":false,"info":"","list":false,"list_add_label":"Add More","name":"use_autoprompt","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":true}},"tool_mode":false}}],["files_and_knowledge",{"Directory":{"base_classes":["DataFrame"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Recursively load files from a directory.","display_name":"Directory","documentation":"https://docs.langflow.org/directory","edited":false,"field_order":["path","types","depth","max_concurrency","load_hidden","recursive","silent_errors","use_multithreading"],"frozen":false,"icon":"folder","legacy":false,"metadata":{"code_hash":"c55e0e29079d","dependencies":{"dependencies":[{"name":"lfx","version":null}],"total_dependencies":1},"module":"lfx.components.files_and_knowledge.directory.DirectoryComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Loaded Files","group_outputs":false,"method":"as_dataframe","name":"dataframe","selected":"DataFrame","tool_mode":true,"types":["DataFrame"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","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":"from lfx.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data, retrieve_file_paths\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, IntInput, MessageTextInput, MultiselectInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.template.field.base import Output\n\n\nclass DirectoryComponent(Component):\n display_name = \"Directory\"\n description = \"Recursively load files from a directory.\"\n documentation: str = \"https://docs.langflow.org/directory\"\n icon = \"folder\"\n name = \"Directory\"\n\n inputs = [\n MessageTextInput(\n name=\"path\",\n display_name=\"Path\",\n info=\"Path to the directory to load files from. Defaults to current directory ('.')\",\n value=\".\",\n tool_mode=True,\n ),\n MultiselectInput(\n name=\"types\",\n display_name=\"File Types\",\n info=\"File types to load. Select one or more types or leave empty to load all supported types.\",\n options=TEXT_FILE_TYPES,\n value=[],\n ),\n IntInput(\n name=\"depth\",\n display_name=\"Depth\",\n info=\"Depth to search for files.\",\n value=0,\n ),\n IntInput(\n name=\"max_concurrency\",\n display_name=\"Max Concurrency\",\n advanced=True,\n info=\"Maximum concurrency for loading files.\",\n value=2,\n ),\n BoolInput(\n name=\"load_hidden\",\n display_name=\"Load Hidden\",\n advanced=True,\n info=\"If true, hidden files will be loaded.\",\n ),\n BoolInput(\n name=\"recursive\",\n display_name=\"Recursive\",\n advanced=True,\n info=\"If true, the search will be recursive.\",\n ),\n BoolInput(\n name=\"silent_errors\",\n display_name=\"Silent Errors\",\n advanced=True,\n info=\"If true, errors will not raise an exception.\",\n ),\n BoolInput(\n name=\"use_multithreading\",\n display_name=\"Use Multithreading\",\n advanced=True,\n info=\"If true, multithreading will be used.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Loaded Files\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def load_directory(self) -> list[Data]:\n path = self.path\n types = self.types\n depth = self.depth\n max_concurrency = self.max_concurrency\n load_hidden = self.load_hidden\n recursive = self.recursive\n silent_errors = self.silent_errors\n use_multithreading = self.use_multithreading\n\n resolved_path = self.resolve_path(path)\n\n # If no types are specified, use all supported types\n if not types:\n types = TEXT_FILE_TYPES\n\n # Check if all specified types are valid\n invalid_types = [t for t in types if t not in TEXT_FILE_TYPES]\n if invalid_types:\n msg = f\"Invalid file types specified: {invalid_types}. Valid types are: {TEXT_FILE_TYPES}\"\n raise ValueError(msg)\n\n valid_types = types\n\n file_paths = retrieve_file_paths(\n resolved_path, load_hidden=load_hidden, recursive=recursive, depth=depth, types=valid_types\n )\n\n loaded_data = []\n if use_multithreading:\n loaded_data = parallel_load_data(file_paths, silent_errors=silent_errors, max_concurrency=max_concurrency)\n else:\n loaded_data = [parse_text_file_to_data(file_path, silent_errors=silent_errors) for file_path in file_paths]\n\n valid_data = [x for x in loaded_data if x is not None and isinstance(x, Data)]\n self.status = valid_data\n return valid_data\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.load_directory())\n"},"depth":{"_input_type":"IntInput","advanced":false,"display_name":"Depth","dynamic":false,"info":"Depth to search for files.","list":false,"list_add_label":"Add More","name":"depth","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":0},"load_hidden":{"_input_type":"BoolInput","advanced":true,"display_name":"Load Hidden","dynamic":false,"info":"If true, hidden files will be loaded.","list":false,"list_add_label":"Add More","name":"load_hidden","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"max_concurrency":{"_input_type":"IntInput","advanced":true,"display_name":"Max Concurrency","dynamic":false,"info":"Maximum concurrency for loading files.","list":false,"list_add_label":"Add More","name":"max_concurrency","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":2},"path":{"_input_type":"MessageTextInput","advanced":false,"display_name":"Path","dynamic":false,"info":"Path to the directory to load files from. Defaults to current directory ('.')","input_types":["Message"],"list":false,"list_add_label":"Add More","load_from_db":false,"name":"path","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":true,"trace_as_input":true,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":"."},"recursive":{"_input_type":"BoolInput","advanced":true,"display_name":"Recursive","dynamic":false,"info":"If true, the search will be recursive.","list":false,"list_add_label":"Add More","name":"recursive","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"silent_errors":{"_input_type":"BoolInput","advanced":true,"display_name":"Silent Errors","dynamic":false,"info":"If true, errors will not raise an exception.","list":false,"list_add_label":"Add More","name":"silent_errors","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"types":{"_input_type":"MultiselectInput","advanced":false,"combobox":false,"display_name":"File Types","dynamic":false,"info":"File types to load. Select one or more types or leave empty to load all supported types.","list":true,"list_add_label":"Add More","name":"types","options":["csv","json","pdf","txt","md","mdx","yaml","yml","xml","html","htm","docx","py","sh","sql","js","ts","tsx"],"override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"toggle":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":false,"type":"str","value":[]},"use_multithreading":{"_input_type":"BoolInput","advanced":true,"display_name":"Use Multithreading","dynamic":false,"info":"If true, multithreading will be used.","list":false,"list_add_label":"Add More","name":"use_multithreading","override_skip":false,"placeholder":"","required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false}},"tool_mode":false},"File":{"base_classes":["Message"],"beta":false,"conditional_paths":[],"custom_fields":{},"description":"Loads and returns the content from uploaded files.","display_name":"Read File","documentation":"https://docs.langflow.org/read-file","edited":false,"field_order":["path","file_path","separator","silent_errors","delete_server_file_after_processing","ignore_unsupported_extensions","ignore_unspecified_files","file_path_str","advanced_mode","pipeline","ocr_engine","md_image_placeholder","md_page_break_placeholder","doc_key","use_multithreading","concurrency_multithreading","markdown"],"frozen":false,"icon":"file-text","legacy":false,"metadata":{"code_hash":"1d81b3a4d764","dependencies":{"dependencies":[{"name":"lfx","version":null},{"name":"langchain_core","version":"0.3.80"},{"name":"pydantic","version":"2.11.10"}],"total_dependencies":3},"module":"lfx.components.files_and_knowledge.file.FileComponent"},"minimized":false,"output_types":[],"outputs":[{"allows_loop":false,"cache":true,"display_name":"Raw Content","group_outputs":false,"method":"load_files_message","name":"message","selected":"Message","tool_mode":true,"types":["Message"],"value":"__UNDEFINED__"}],"pinned":false,"template":{"_type":"Component","advanced_mode":{"_input_type":"BoolInput","advanced":false,"display_name":"Advanced Parser","dynamic":false,"info":"Enable advanced document processing and export with Docling for PDFs, images, and office documents. Note that advanced document processing can consume significant resources.","list":false,"list_add_label":"Add More","name":"advanced_mode","override_skip":false,"placeholder":"","real_time_refresh":true,"required":false,"show":true,"title_case":false,"tool_mode":false,"trace_as_metadata":true,"track_in_telemetry":true,"type":"bool","value":false},"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":"\"\"\"Enhanced file component with Docling support and process isolation.\n\nNotes:\n-----\n- ALL Docling parsing/export runs in a separate OS process to prevent memory\n growth and native library state from impacting the main Langflow process.\n- Standard text/structured parsing continues to use existing BaseFileComponent\n utilities (and optional threading via `parallel_load_data`).\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport json\nimport subprocess\nimport sys\nimport textwrap\nfrom copy import deepcopy\nfrom pathlib import Path\nfrom tempfile import NamedTemporaryFile\nfrom typing import Any\n\nfrom lfx.base.data.base_file import BaseFileComponent\nfrom lfx.base.data.storage_utils import parse_storage_path, validate_image_content_type\nfrom lfx.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data\nfrom lfx.inputs.inputs import DropdownInput, MessageTextInput, StrInput\nfrom lfx.io import BoolInput, FileInput, IntInput, Output\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame # noqa: TC001\nfrom lfx.schema.message import Message\nfrom lfx.services.deps import get_settings_service, get_storage_service\nfrom lfx.utils.async_helpers import run_until_complete\n\n\nclass FileComponent(BaseFileComponent):\n \"\"\"File component with optional Docling processing (isolated in a subprocess).\"\"\"\n\n display_name = \"Read File\"\n # description is now a dynamic property - see get_tool_description()\n _base_description = \"Loads content from one or more files.\"\n documentation: str = \"https://docs.langflow.org/read-file\"\n icon = \"file-text\"\n name = \"File\"\n add_tool_output = True # Enable tool mode toggle without requiring tool_mode inputs\n\n # Extensions that can be processed without Docling (using standard text parsing)\n TEXT_EXTENSIONS = TEXT_FILE_TYPES\n\n # Extensions that require Docling for processing (images, advanced office formats, etc.)\n DOCLING_ONLY_EXTENSIONS = [\n \"adoc\",\n \"asciidoc\",\n \"asc\",\n \"bmp\",\n \"dotx\",\n \"dotm\",\n \"docm\",\n \"jpg\",\n \"jpeg\",\n \"png\",\n \"potx\",\n \"ppsx\",\n \"pptm\",\n \"potm\",\n \"ppsm\",\n \"pptx\",\n \"tiff\",\n \"xls\",\n \"xlsx\",\n \"xhtml\",\n \"webp\",\n ]\n\n # Docling-supported/compatible extensions; TEXT_FILE_TYPES are supported by the base loader.\n VALID_EXTENSIONS = [\n *TEXT_EXTENSIONS,\n *DOCLING_ONLY_EXTENSIONS,\n ]\n\n # Fixed export settings used when markdown export is requested.\n EXPORT_FORMAT = \"Markdown\"\n IMAGE_MODE = \"placeholder\"\n\n _base_inputs = deepcopy(BaseFileComponent.get_base_inputs())\n\n for input_item in _base_inputs:\n if isinstance(input_item, FileInput) and input_item.name == \"path\":\n input_item.real_time_refresh = True\n input_item.tool_mode = False # Disable tool mode for file upload input\n input_item.required = False # Make it optional so it doesn't error in tool mode\n break\n\n inputs = [\n *_base_inputs,\n StrInput(\n name=\"file_path_str\",\n display_name=\"File Path\",\n info=(\n \"Path to the file to read. Used when component is called as a tool. \"\n \"If not provided, will use the uploaded file from 'path' input.\"\n ),\n show=False,\n advanced=True,\n tool_mode=True, # Required for Toolset toggle, but _get_tools() ignores this parameter\n required=False,\n ),\n BoolInput(\n name=\"advanced_mode\",\n display_name=\"Advanced Parser\",\n value=False,\n real_time_refresh=True,\n info=(\n \"Enable advanced document processing and export with Docling for PDFs, images, and office documents. \"\n \"Note that advanced document processing can consume significant resources.\"\n ),\n show=True,\n ),\n DropdownInput(\n name=\"pipeline\",\n display_name=\"Pipeline\",\n info=\"Docling pipeline to use\",\n options=[\"standard\", \"vlm\"],\n value=\"standard\",\n advanced=True,\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"ocr_engine\",\n display_name=\"OCR Engine\",\n info=\"OCR engine to use. Only available when pipeline is set to 'standard'.\",\n options=[\"None\", \"easyocr\"],\n value=\"easyocr\",\n show=False,\n advanced=True,\n ),\n StrInput(\n name=\"md_image_placeholder\",\n display_name=\"Image placeholder\",\n info=\"Specify the image placeholder for markdown exports.\",\n value=\"\",\n advanced=True,\n show=False,\n ),\n StrInput(\n name=\"md_page_break_placeholder\",\n display_name=\"Page break placeholder\",\n info=\"Add this placeholder between pages in the markdown output.\",\n value=\"\",\n advanced=True,\n show=False,\n ),\n MessageTextInput(\n name=\"doc_key\",\n display_name=\"Doc Key\",\n info=\"The key to use for the DoclingDocument column.\",\n value=\"doc\",\n advanced=True,\n show=False,\n ),\n # Deprecated input retained for backward-compatibility.\n BoolInput(\n name=\"use_multithreading\",\n display_name=\"[Deprecated] Use Multithreading\",\n advanced=True,\n value=True,\n info=\"Set 'Processing Concurrency' greater than 1 to enable multithreading.\",\n ),\n IntInput(\n name=\"concurrency_multithreading\",\n display_name=\"Processing Concurrency\",\n advanced=True,\n info=\"When multiple files are being processed, the number of files to process concurrently.\",\n value=1,\n ),\n BoolInput(\n name=\"markdown\",\n display_name=\"Markdown Export\",\n info=\"Export processed documents to Markdown format. Only available when advanced mode is enabled.\",\n value=False,\n show=False,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Raw Content\", name=\"message\", method=\"load_files_message\", tool_mode=True),\n ]\n\n # ------------------------------ Tool description with file names --------------\n\n def get_tool_description(self) -> str:\n \"\"\"Return a dynamic description that includes the names of uploaded files.\n\n This helps the Agent understand which files are available to read.\n \"\"\"\n base_description = \"Loads and returns the content from uploaded files.\"\n\n # Get the list of uploaded file paths\n file_paths = getattr(self, \"path\", None)\n if not file_paths:\n return base_description\n\n # Ensure it's a list\n if not isinstance(file_paths, list):\n file_paths = [file_paths]\n\n # Extract just the file names from the paths\n file_names = []\n for fp in file_paths:\n if fp:\n name = Path(fp).name\n file_names.append(name)\n\n if file_names:\n files_str = \", \".join(file_names)\n return f\"{base_description} Available files: {files_str}. Call this tool to read these files.\"\n\n return base_description\n\n @property\n def description(self) -> str:\n \"\"\"Dynamic description property that includes uploaded file names.\"\"\"\n return self.get_tool_description()\n\n async def _get_tools(self) -> list:\n \"\"\"Override to create a tool without parameters.\n\n The Read File component should use the files already uploaded via UI,\n not accept file paths from the Agent (which wouldn't know the internal paths).\n \"\"\"\n from langchain_core.tools import StructuredTool\n from pydantic import BaseModel\n\n # Empty schema - no parameters needed\n class EmptySchema(BaseModel):\n \"\"\"No parameters required - uses pre-uploaded files.\"\"\"\n\n async def read_files_tool() -> str:\n \"\"\"Read the content of uploaded files.\"\"\"\n try:\n result = self.load_files_message()\n if hasattr(result, \"get_text\"):\n return result.get_text()\n if hasattr(result, \"text\"):\n return result.text\n return str(result)\n except (FileNotFoundError, ValueError, OSError, RuntimeError) as e:\n return f\"Error reading files: {e}\"\n\n description = self.get_tool_description()\n\n tool = StructuredTool(\n name=\"load_files_message\",\n description=description,\n coroutine=read_files_tool,\n args_schema=EmptySchema,\n handle_tool_error=True,\n tags=[\"load_files_message\"],\n metadata={\n \"display_name\": \"Read File\",\n \"display_description\": description,\n },\n )\n\n return [tool]\n\n # ------------------------------ UI helpers --------------------------------------\n\n def _path_value(self, template: dict) -> list[str]:\n \"\"\"Return the list of currently selected file paths from the template.\"\"\"\n return template.get(\"path\", {}).get(\"file_path\", [])\n\n def update_build_config(\n self,\n build_config: dict[str, Any],\n field_value: Any,\n field_name: str | None = None,\n ) -> dict[str, Any]:\n \"\"\"Show/hide Advanced Parser and related fields based on selection context.\"\"\"\n if field_name == \"path\":\n paths = self._path_value(build_config)\n\n # If all files can be processed by docling, do so\n allow_advanced = all(not file_path.endswith((\".csv\", \".xlsx\", \".parquet\")) for file_path in paths)\n build_config[\"advanced_mode\"][\"show\"] = allow_advanced\n if not allow_advanced:\n build_config[\"advanced_mode\"][\"value\"] = False\n for f in (\"pipeline\", \"ocr_engine\", \"doc_key\", \"md_image_placeholder\", \"md_page_break_placeholder\"):\n if f in build_config:\n build_config[f][\"show\"] = False\n\n # Docling Processing\n elif field_name == \"advanced_mode\":\n for f in (\"pipeline\", \"ocr_engine\", \"doc_key\", \"md_image_placeholder\", \"md_page_break_placeholder\"):\n if f in build_config:\n build_config[f][\"show\"] = bool(field_value)\n if f == \"pipeline\":\n build_config[f][\"advanced\"] = not bool(field_value)\n\n elif field_name == \"pipeline\":\n if field_value == \"standard\":\n build_config[\"ocr_engine\"][\"show\"] = True\n build_config[\"ocr_engine\"][\"value\"] = \"easyocr\"\n else:\n build_config[\"ocr_engine\"][\"show\"] = False\n build_config[\"ocr_engine\"][\"value\"] = \"None\"\n\n return build_config\n\n def update_outputs(self, frontend_node: dict[str, Any], field_name: str, field_value: Any) -> dict[str, Any]: # noqa: ARG002\n \"\"\"Dynamically show outputs based on file count/type and advanced mode.\"\"\"\n if field_name not in [\"path\", \"advanced_mode\", \"pipeline\"]:\n return frontend_node\n\n template = frontend_node.get(\"template\", {})\n paths = self._path_value(template)\n if not paths:\n return frontend_node\n\n frontend_node[\"outputs\"] = []\n if len(paths) == 1:\n file_path = paths[0] if field_name == \"path\" else frontend_node[\"template\"][\"path\"][\"file_path\"][0]\n if file_path.endswith((\".csv\", \".xlsx\", \".parquet\")):\n frontend_node[\"outputs\"].append(\n Output(\n display_name=\"Structured Content\",\n name=\"dataframe\",\n method=\"load_files_structured\",\n tool_mode=True,\n ),\n )\n elif file_path.endswith(\".json\"):\n frontend_node[\"outputs\"].append(\n Output(display_name=\"Structured Content\", name=\"json\", method=\"load_files_json\", tool_mode=True),\n )\n\n advanced_mode = frontend_node.get(\"template\", {}).get(\"advanced_mode\", {}).get(\"value\", False)\n if advanced_mode:\n frontend_node[\"outputs\"].append(\n Output(\n display_name=\"Structured Output\",\n name=\"advanced_dataframe\",\n method=\"load_files_dataframe\",\n tool_mode=True,\n ),\n )\n frontend_node[\"outputs\"].append(\n Output(\n display_name=\"Markdown\", name=\"advanced_markdown\", method=\"load_files_markdown\", tool_mode=True\n ),\n )\n frontend_node[\"outputs\"].append(\n Output(display_name=\"File Path\", name=\"path\", method=\"load_files_path\", tool_mode=True),\n )\n else:\n frontend_node[\"outputs\"].append(\n Output(display_name=\"Raw Content\", name=\"message\", method=\"load_files_message\", tool_mode=True),\n )\n frontend_node[\"outputs\"].append(\n Output(display_name=\"File Path\", name=\"path\", method=\"load_files_path\", tool_mode=True),\n )\n else:\n # Multiple files => DataFrame output; advanced parser disabled\n frontend_node[\"outputs\"].append(\n Output(display_name=\"Files\", name=\"dataframe\", method=\"load_files\", tool_mode=True)\n )\n\n return frontend_node\n\n # ------------------------------ Core processing ----------------------------------\n\n def _validate_and_resolve_paths(self) -> list[BaseFileComponent.BaseFile]:\n \"\"\"Override to handle file_path_str input from tool mode.\n\n When called as a tool, the file_path_str parameter can be set.\n If not provided, it will fall back to using the path FileInput (uploaded file).\n Priority:\n 1. file_path_str (if provided by the tool call)\n 2. path (uploaded file from UI)\n \"\"\"\n # Check if file_path_str is provided (from tool mode)\n file_path_str = getattr(self, \"file_path_str\", None)\n if file_path_str:\n # Use the string path from tool mode\n from pathlib import Path\n\n from lfx.schema.data import Data\n\n resolved_path = Path(self.resolve_path(file_path_str))\n if not resolved_path.exists():\n msg = f\"File or directory not found: {file_path_str}\"\n self.log(msg)\n if not self.silent_errors:\n raise ValueError(msg)\n return []\n\n data_obj = Data(data={self.SERVER_FILE_PATH_FIELDNAME: str(resolved_path)})\n return [BaseFileComponent.BaseFile(data_obj, resolved_path, delete_after_processing=False)]\n\n # Otherwise use the default implementation (uses path FileInput)\n return super()._validate_and_resolve_paths()\n\n def _is_docling_compatible(self, file_path: str) -> bool:\n \"\"\"Lightweight extension gate for Docling-compatible types.\"\"\"\n docling_exts = (\n \".adoc\",\n \".asciidoc\",\n \".asc\",\n \".bmp\",\n \".csv\",\n \".dotx\",\n \".dotm\",\n \".docm\",\n \".docx\",\n \".htm\",\n \".html\",\n \".jpg\",\n \".jpeg\",\n \".json\",\n \".md\",\n \".pdf\",\n \".png\",\n \".potx\",\n \".ppsx\",\n \".pptm\",\n \".potm\",\n \".ppsm\",\n \".pptx\",\n \".tiff\",\n \".txt\",\n \".xls\",\n \".xlsx\",\n \".xhtml\",\n \".xml\",\n \".webp\",\n )\n return file_path.lower().endswith(docling_exts)\n\n async def _get_local_file_for_docling(self, file_path: str) -> tuple[str, bool]:\n \"\"\"Get a local file path for Docling processing, downloading from S3 if needed.\n\n Args:\n file_path: Either a local path or S3 key (format \"flow_id/filename\")\n\n Returns:\n tuple[str, bool]: (local_path, should_delete) where should_delete indicates\n if this is a temporary file that should be cleaned up\n \"\"\"\n settings = get_settings_service().settings\n if settings.storage_type == \"local\":\n return file_path, False\n\n # S3 storage - download to temp file\n parsed = parse_storage_path(file_path)\n if not parsed:\n msg = f\"Invalid S3 path format: {file_path}. Expected 'flow_id/filename'\"\n raise ValueError(msg)\n\n storage_service = get_storage_service()\n flow_id, filename = parsed\n\n # Get file content from S3\n content = await storage_service.get_file(flow_id, filename)\n\n suffix = Path(filename).suffix\n with NamedTemporaryFile(mode=\"wb\", suffix=suffix, delete=False) as tmp_file:\n tmp_file.write(content)\n temp_path = tmp_file.name\n\n return temp_path, True\n\n def _process_docling_in_subprocess(self, file_path: str) -> Data | None:\n \"\"\"Run Docling in a separate OS process and map the result to a Data object.\n\n We avoid multiprocessing pickling by launching `python -c \"