lfx-nightly 0.2.0.dev0__py3-none-any.whl → 0.2.0.dev41__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. lfx/_assets/component_index.json +1 -1
  2. lfx/base/agents/agent.py +21 -4
  3. lfx/base/agents/altk_base_agent.py +393 -0
  4. lfx/base/agents/altk_tool_wrappers.py +565 -0
  5. lfx/base/agents/events.py +2 -1
  6. lfx/base/composio/composio_base.py +159 -224
  7. lfx/base/data/base_file.py +97 -20
  8. lfx/base/data/docling_utils.py +61 -10
  9. lfx/base/data/storage_utils.py +301 -0
  10. lfx/base/data/utils.py +178 -14
  11. lfx/base/mcp/util.py +2 -2
  12. lfx/base/models/anthropic_constants.py +21 -12
  13. lfx/base/models/groq_constants.py +74 -58
  14. lfx/base/models/groq_model_discovery.py +265 -0
  15. lfx/base/models/model.py +1 -1
  16. lfx/base/models/model_utils.py +100 -0
  17. lfx/base/models/openai_constants.py +7 -0
  18. lfx/base/models/watsonx_constants.py +32 -8
  19. lfx/base/tools/run_flow.py +601 -129
  20. lfx/cli/commands.py +9 -4
  21. lfx/cli/common.py +2 -2
  22. lfx/cli/run.py +1 -1
  23. lfx/cli/script_loader.py +53 -11
  24. lfx/components/Notion/create_page.py +1 -1
  25. lfx/components/Notion/list_database_properties.py +1 -1
  26. lfx/components/Notion/list_pages.py +1 -1
  27. lfx/components/Notion/list_users.py +1 -1
  28. lfx/components/Notion/page_content_viewer.py +1 -1
  29. lfx/components/Notion/search.py +1 -1
  30. lfx/components/Notion/update_page_property.py +1 -1
  31. lfx/components/__init__.py +19 -5
  32. lfx/components/{agents → altk}/__init__.py +5 -9
  33. lfx/components/altk/altk_agent.py +193 -0
  34. lfx/components/apify/apify_actor.py +1 -1
  35. lfx/components/composio/__init__.py +70 -18
  36. lfx/components/composio/apollo_composio.py +11 -0
  37. lfx/components/composio/bitbucket_composio.py +11 -0
  38. lfx/components/composio/canva_composio.py +11 -0
  39. lfx/components/composio/coda_composio.py +11 -0
  40. lfx/components/composio/composio_api.py +10 -0
  41. lfx/components/composio/discord_composio.py +1 -1
  42. lfx/components/composio/elevenlabs_composio.py +11 -0
  43. lfx/components/composio/exa_composio.py +11 -0
  44. lfx/components/composio/firecrawl_composio.py +11 -0
  45. lfx/components/composio/fireflies_composio.py +11 -0
  46. lfx/components/composio/gmail_composio.py +1 -1
  47. lfx/components/composio/googlebigquery_composio.py +11 -0
  48. lfx/components/composio/googlecalendar_composio.py +1 -1
  49. lfx/components/composio/googledocs_composio.py +1 -1
  50. lfx/components/composio/googlemeet_composio.py +1 -1
  51. lfx/components/composio/googlesheets_composio.py +1 -1
  52. lfx/components/composio/googletasks_composio.py +1 -1
  53. lfx/components/composio/heygen_composio.py +11 -0
  54. lfx/components/composio/mem0_composio.py +11 -0
  55. lfx/components/composio/peopledatalabs_composio.py +11 -0
  56. lfx/components/composio/perplexityai_composio.py +11 -0
  57. lfx/components/composio/serpapi_composio.py +11 -0
  58. lfx/components/composio/slack_composio.py +3 -574
  59. lfx/components/composio/slackbot_composio.py +1 -1
  60. lfx/components/composio/snowflake_composio.py +11 -0
  61. lfx/components/composio/tavily_composio.py +11 -0
  62. lfx/components/composio/youtube_composio.py +2 -2
  63. lfx/components/cuga/__init__.py +34 -0
  64. lfx/components/cuga/cuga_agent.py +730 -0
  65. lfx/components/data/__init__.py +78 -28
  66. lfx/components/data_source/__init__.py +58 -0
  67. lfx/components/{data → data_source}/api_request.py +26 -3
  68. lfx/components/{data → data_source}/csv_to_data.py +15 -10
  69. lfx/components/{data → data_source}/json_to_data.py +15 -8
  70. lfx/components/{data → data_source}/news_search.py +1 -1
  71. lfx/components/{data → data_source}/rss.py +1 -1
  72. lfx/components/{data → data_source}/sql_executor.py +1 -1
  73. lfx/components/{data → data_source}/url.py +1 -1
  74. lfx/components/{data → data_source}/web_search.py +1 -1
  75. lfx/components/datastax/astradb_cql.py +1 -1
  76. lfx/components/datastax/astradb_graph.py +1 -1
  77. lfx/components/datastax/astradb_tool.py +1 -1
  78. lfx/components/datastax/astradb_vectorstore.py +1 -1
  79. lfx/components/datastax/hcd.py +1 -1
  80. lfx/components/deactivated/json_document_builder.py +1 -1
  81. lfx/components/docling/__init__.py +0 -3
  82. lfx/components/docling/chunk_docling_document.py +3 -1
  83. lfx/components/docling/export_docling_document.py +3 -1
  84. lfx/components/elastic/elasticsearch.py +1 -1
  85. lfx/components/files_and_knowledge/__init__.py +47 -0
  86. lfx/components/{data → files_and_knowledge}/directory.py +1 -1
  87. lfx/components/{data → files_and_knowledge}/file.py +304 -24
  88. lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +2 -2
  89. lfx/components/{data → files_and_knowledge}/save_file.py +218 -31
  90. lfx/components/flow_controls/__init__.py +58 -0
  91. lfx/components/{logic → flow_controls}/conditional_router.py +1 -1
  92. lfx/components/{logic → flow_controls}/loop.py +43 -9
  93. lfx/components/flow_controls/run_flow.py +108 -0
  94. lfx/components/glean/glean_search_api.py +1 -1
  95. lfx/components/groq/groq.py +35 -28
  96. lfx/components/helpers/__init__.py +102 -0
  97. lfx/components/ibm/watsonx.py +7 -1
  98. lfx/components/input_output/__init__.py +3 -1
  99. lfx/components/input_output/chat.py +4 -3
  100. lfx/components/input_output/chat_output.py +10 -4
  101. lfx/components/input_output/text.py +1 -1
  102. lfx/components/input_output/text_output.py +1 -1
  103. lfx/components/{data → input_output}/webhook.py +1 -1
  104. lfx/components/knowledge_bases/__init__.py +59 -4
  105. lfx/components/langchain_utilities/character.py +1 -1
  106. lfx/components/langchain_utilities/csv_agent.py +84 -16
  107. lfx/components/langchain_utilities/json_agent.py +67 -12
  108. lfx/components/langchain_utilities/language_recursive.py +1 -1
  109. lfx/components/llm_operations/__init__.py +46 -0
  110. lfx/components/{processing → llm_operations}/batch_run.py +17 -8
  111. lfx/components/{processing → llm_operations}/lambda_filter.py +1 -1
  112. lfx/components/{logic → llm_operations}/llm_conditional_router.py +1 -1
  113. lfx/components/{processing/llm_router.py → llm_operations/llm_selector.py} +3 -3
  114. lfx/components/{processing → llm_operations}/structured_output.py +1 -1
  115. lfx/components/logic/__init__.py +126 -0
  116. lfx/components/mem0/mem0_chat_memory.py +11 -0
  117. lfx/components/models/__init__.py +64 -9
  118. lfx/components/models_and_agents/__init__.py +49 -0
  119. lfx/components/{agents → models_and_agents}/agent.py +6 -4
  120. lfx/components/models_and_agents/embedding_model.py +353 -0
  121. lfx/components/models_and_agents/language_model.py +398 -0
  122. lfx/components/{agents → models_and_agents}/mcp_component.py +53 -44
  123. lfx/components/{helpers → models_and_agents}/memory.py +1 -1
  124. lfx/components/nvidia/system_assist.py +1 -1
  125. lfx/components/olivya/olivya.py +1 -1
  126. lfx/components/ollama/ollama.py +24 -5
  127. lfx/components/processing/__init__.py +9 -60
  128. lfx/components/processing/converter.py +1 -1
  129. lfx/components/processing/dataframe_operations.py +1 -1
  130. lfx/components/processing/parse_json_data.py +2 -2
  131. lfx/components/processing/parser.py +1 -1
  132. lfx/components/processing/split_text.py +1 -1
  133. lfx/components/qdrant/qdrant.py +1 -1
  134. lfx/components/redis/redis.py +1 -1
  135. lfx/components/twelvelabs/split_video.py +10 -0
  136. lfx/components/twelvelabs/video_file.py +12 -0
  137. lfx/components/utilities/__init__.py +43 -0
  138. lfx/components/{helpers → utilities}/calculator_core.py +1 -1
  139. lfx/components/{helpers → utilities}/current_date.py +1 -1
  140. lfx/components/{processing → utilities}/python_repl_core.py +1 -1
  141. lfx/components/vectorstores/local_db.py +9 -0
  142. lfx/components/youtube/youtube_transcripts.py +118 -30
  143. lfx/custom/custom_component/component.py +57 -1
  144. lfx/custom/custom_component/custom_component.py +68 -6
  145. lfx/custom/directory_reader/directory_reader.py +5 -2
  146. lfx/graph/edge/base.py +43 -20
  147. lfx/graph/state/model.py +15 -2
  148. lfx/graph/utils.py +6 -0
  149. lfx/graph/vertex/param_handler.py +10 -7
  150. lfx/helpers/__init__.py +12 -0
  151. lfx/helpers/flow.py +117 -0
  152. lfx/inputs/input_mixin.py +24 -1
  153. lfx/inputs/inputs.py +13 -1
  154. lfx/interface/components.py +161 -83
  155. lfx/log/logger.py +5 -3
  156. lfx/schema/image.py +2 -12
  157. lfx/services/database/__init__.py +5 -0
  158. lfx/services/database/service.py +25 -0
  159. lfx/services/deps.py +87 -22
  160. lfx/services/interfaces.py +5 -0
  161. lfx/services/manager.py +24 -10
  162. lfx/services/mcp_composer/service.py +1029 -162
  163. lfx/services/session.py +5 -0
  164. lfx/services/settings/auth.py +18 -11
  165. lfx/services/settings/base.py +56 -30
  166. lfx/services/settings/constants.py +8 -0
  167. lfx/services/storage/local.py +108 -46
  168. lfx/services/storage/service.py +171 -29
  169. lfx/template/field/base.py +3 -0
  170. lfx/utils/image.py +29 -11
  171. lfx/utils/ssrf_protection.py +384 -0
  172. lfx/utils/validate_cloud.py +26 -0
  173. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/METADATA +38 -22
  174. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/RECORD +189 -160
  175. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/WHEEL +1 -1
  176. lfx/components/agents/altk_agent.py +0 -366
  177. lfx/components/agents/cuga_agent.py +0 -1013
  178. lfx/components/docling/docling_remote_vlm.py +0 -284
  179. lfx/components/logic/run_flow.py +0 -71
  180. lfx/components/models/embedding_model.py +0 -195
  181. lfx/components/models/language_model.py +0 -144
  182. lfx/components/processing/dataframe_to_toolset.py +0 -259
  183. /lfx/components/{data → data_source}/mock_data.py +0 -0
  184. /lfx/components/{knowledge_bases → files_and_knowledge}/ingestion.py +0 -0
  185. /lfx/components/{logic → flow_controls}/data_conditional_router.py +0 -0
  186. /lfx/components/{logic → flow_controls}/flow_tool.py +0 -0
  187. /lfx/components/{logic → flow_controls}/listen.py +0 -0
  188. /lfx/components/{logic → flow_controls}/notify.py +0 -0
  189. /lfx/components/{logic → flow_controls}/pass_message.py +0 -0
  190. /lfx/components/{logic → flow_controls}/sub_flow.py +0 -0
  191. /lfx/components/{processing → models_and_agents}/prompt.py +0 -0
  192. /lfx/components/{helpers → processing}/create_list.py +0 -0
  193. /lfx/components/{helpers → processing}/output_parser.py +0 -0
  194. /lfx/components/{helpers → processing}/store_message.py +0 -0
  195. /lfx/components/{helpers → utilities}/id_generator.py +0 -0
  196. {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,108 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+ from lfx.base.tools.run_flow import RunFlowBaseComponent
5
+ from lfx.log.logger import logger
6
+ from lfx.schema.data import Data
7
+ from lfx.schema.dotdict import dotdict
8
+
9
+
10
+ class RunFlowComponent(RunFlowBaseComponent):
11
+ display_name = "Run Flow"
12
+ description = (
13
+ "Executes another flow from within the same project. Can also be used as a tool for agents."
14
+ " \n **Select a Flow to use the tool mode**"
15
+ )
16
+ documentation: str = "https://docs.langflow.org/run-flow"
17
+ beta = True
18
+ name = "RunFlow"
19
+ icon = "Workflow"
20
+
21
+ inputs = RunFlowBaseComponent.get_base_inputs()
22
+ outputs = RunFlowBaseComponent.get_base_outputs()
23
+
24
+ async def update_build_config(
25
+ self,
26
+ build_config: dotdict,
27
+ field_value: Any,
28
+ field_name: str | None = None,
29
+ ):
30
+ missing_keys = [key for key in self.default_keys if key not in build_config]
31
+ for key in missing_keys:
32
+ if key == "flow_name_selected":
33
+ build_config[key] = {"options": [], "options_metadata": [], "value": None}
34
+ elif key == "flow_id_selected":
35
+ build_config[key] = {"value": None}
36
+ elif key == "cache_flow":
37
+ build_config[key] = {"value": False}
38
+ else:
39
+ build_config[key] = {}
40
+ if field_name == "flow_name_selected" and (build_config.get("is_refresh", False) or field_value is None):
41
+ # refresh button was clicked or componented was initialized, so list the flows
42
+ options: list[str] = await self.alist_flows_by_flow_folder()
43
+ build_config["flow_name_selected"]["options"] = [flow.data["name"] for flow in options]
44
+ build_config["flow_name_selected"]["options_metadata"] = []
45
+ for flow in options:
46
+ # populate options_metadata
47
+ build_config["flow_name_selected"]["options_metadata"].append(
48
+ {"id": flow.data["id"], "updated_at": flow.data["updated_at"]}
49
+ )
50
+ # update selected flow if it is stale
51
+ if str(flow.data["id"]) == self.flow_id_selected:
52
+ await self.check_and_update_stale_flow(flow, build_config)
53
+ elif field_name in {"flow_name_selected", "flow_id_selected"} and field_value is not None:
54
+ # flow was selected by name or id, so get the flow and update the bcfg
55
+ try:
56
+ # derive flow id if the field_name is flow_name_selected
57
+ build_config["flow_id_selected"]["value"] = (
58
+ self.get_selected_flow_meta(build_config, "id") or build_config["flow_id_selected"]["value"]
59
+ )
60
+ updated_at = self.get_selected_flow_meta(build_config, "updated_at")
61
+ await self.load_graph_and_update_cfg(
62
+ build_config, build_config["flow_id_selected"]["value"], updated_at
63
+ )
64
+ except Exception as e:
65
+ msg = f"Error building graph for flow {field_value}"
66
+ await logger.aexception(msg)
67
+ raise RuntimeError(msg) from e
68
+
69
+ return build_config
70
+
71
+ def get_selected_flow_meta(self, build_config: dotdict, field: str) -> dict:
72
+ """Get the selected flow's metadata from the build config."""
73
+ return build_config.get("flow_name_selected", {}).get("selected_metadata", {}).get(field)
74
+
75
+ async def load_graph_and_update_cfg(
76
+ self,
77
+ build_config: dotdict,
78
+ flow_id: str,
79
+ updated_at: str | datetime,
80
+ ) -> None:
81
+ """Load a flow's graph and update the build config."""
82
+ graph = await self.get_graph(
83
+ flow_id_selected=flow_id,
84
+ updated_at=self.get_str_isots(updated_at),
85
+ )
86
+ self.update_build_config_from_graph(build_config, graph)
87
+
88
+ def should_update_stale_flow(self, flow: Data, build_config: dotdict) -> bool:
89
+ """Check if the flow should be updated."""
90
+ return (
91
+ (updated_at := self.get_str_isots(flow.data["updated_at"])) # true updated_at date just fetched from db
92
+ and (stale_at := self.get_selected_flow_meta(build_config, "updated_at")) # outdated date in bcfg
93
+ and self._parse_timestamp(updated_at) > self._parse_timestamp(stale_at) # stale flow condition
94
+ )
95
+
96
+ async def check_and_update_stale_flow(self, flow: Data, build_config: dotdict) -> None:
97
+ """Check if the flow should be updated and update it if necessary."""
98
+ # TODO: improve contract/return value
99
+ if self.should_update_stale_flow(flow, build_config):
100
+ await self.load_graph_and_update_cfg(
101
+ build_config,
102
+ flow.data["id"],
103
+ flow.data["updated_at"],
104
+ )
105
+
106
+ def get_str_isots(self, date: datetime | str) -> str:
107
+ """Get a string timestamp from a datetime or string."""
108
+ return date.isoformat() if hasattr(date, "isoformat") else date
@@ -101,7 +101,7 @@ class GleanAPIWrapper(BaseModel):
101
101
  class GleanSearchAPIComponent(LCToolComponent):
102
102
  display_name: str = "Glean Search API"
103
103
  description: str = "Search using Glean's API."
104
- documentation: str = "https://docs.langflow.org/Components/components-tools#glean-search-api"
104
+ documentation: str = "https://docs.langflow.org/bundles-glean"
105
105
  icon: str = "Glean"
106
106
 
107
107
  outputs = [
@@ -1,7 +1,7 @@
1
- import requests
2
1
  from pydantic.v1 import SecretStr
3
2
 
4
- from lfx.base.models.groq_constants import GROQ_MODELS, TOOL_CALLING_UNSUPPORTED_GROQ_MODELS, UNSUPPORTED_GROQ_MODELS
3
+ from lfx.base.models.groq_constants import GROQ_MODELS
4
+ from lfx.base.models.groq_model_discovery import get_groq_models
5
5
  from lfx.base.models.model import LCModelComponent
6
6
  from lfx.field_typing import LanguageModel
7
7
  from lfx.field_typing.range_spec import RangeSpec
@@ -52,7 +52,7 @@ class GroqModel(LCModelComponent):
52
52
  DropdownInput(
53
53
  name="model_name",
54
54
  display_name="Model",
55
- info="The name of the model to use.",
55
+ info="The name of the model to use. Add your Groq API key to access additional available models.",
56
56
  options=GROQ_MODELS,
57
57
  value=GROQ_MODELS[0],
58
58
  refresh_button=True,
@@ -71,35 +71,42 @@ class GroqModel(LCModelComponent):
71
71
  ]
72
72
 
73
73
  def get_models(self, *, tool_model_enabled: bool | None = None) -> list[str]:
74
+ """Get available Groq models using the dynamic discovery system.
75
+
76
+ This method uses the groq_model_discovery module which:
77
+ - Fetches models directly from Groq API
78
+ - Automatically tests tool calling support
79
+ - Caches results for 24 hours
80
+ - Falls back to hardcoded list if API fails
81
+
82
+ Args:
83
+ tool_model_enabled: If True, only return models that support tool calling
84
+
85
+ Returns:
86
+ List of available model IDs
87
+ """
74
88
  try:
75
- url = f"{self.base_url}/openai/v1/models"
76
- headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
89
+ # Get models with metadata from dynamic discovery system
90
+ api_key = self.api_key if hasattr(self, "api_key") and self.api_key else None
91
+ models_metadata = get_groq_models(api_key=api_key)
77
92
 
78
- response = requests.get(url, headers=headers, timeout=10)
79
- response.raise_for_status()
80
- model_list = response.json()
93
+ # Filter out non-LLM models (audio, TTS, guards)
81
94
  model_ids = [
82
- model["id"] for model in model_list.get("data", []) if model["id"] not in UNSUPPORTED_GROQ_MODELS
95
+ model_id for model_id, metadata in models_metadata.items() if not metadata.get("not_supported", False)
83
96
  ]
84
- except (ImportError, ValueError, requests.exceptions.RequestException) as e:
97
+
98
+ # Filter by tool calling support if requested
99
+ if tool_model_enabled:
100
+ model_ids = [model_id for model_id in model_ids if models_metadata[model_id].get("tool_calling", False)]
101
+ logger.info(f"Loaded {len(model_ids)} Groq models with tool calling support")
102
+ else:
103
+ logger.info(f"Loaded {len(model_ids)} Groq models")
104
+ except (ValueError, KeyError, TypeError, ImportError) as e:
85
105
  logger.exception(f"Error getting model names: {e}")
86
- model_ids = GROQ_MODELS
87
- if tool_model_enabled:
88
- try:
89
- from langchain_groq import ChatGroq
90
- except ImportError as e:
91
- msg = "langchain_groq is not installed. Please install it with `pip install langchain_groq`."
92
- raise ImportError(msg) from e
93
- for model in model_ids:
94
- model_with_tool = ChatGroq(
95
- model=model,
96
- api_key=self.api_key,
97
- base_url=self.base_url,
98
- )
99
- if not self.supports_tool_calling(model_with_tool) or model in TOOL_CALLING_UNSUPPORTED_GROQ_MODELS:
100
- model_ids.remove(model)
106
+ # Fallback to hardcoded list from groq_constants.py
107
+ return GROQ_MODELS
108
+ else:
101
109
  return model_ids
102
- return model_ids
103
110
 
104
111
  def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
105
112
  if field_name in {"base_url", "model_name", "tool_model_enabled", "api_key"} and field_value:
@@ -107,13 +114,13 @@ class GroqModel(LCModelComponent):
107
114
  if len(self.api_key) != 0:
108
115
  try:
109
116
  ids = self.get_models(tool_model_enabled=self.tool_model_enabled)
110
- except (ImportError, ValueError, requests.exceptions.RequestException) as e:
117
+ except (ValueError, KeyError, TypeError, ImportError) as e:
111
118
  logger.exception(f"Error getting model names: {e}")
112
119
  ids = GROQ_MODELS
113
120
  build_config.setdefault("model_name", {})
114
121
  build_config["model_name"]["options"] = ids
115
122
  build_config["model_name"].setdefault("value", ids[0])
116
- except Exception as e:
123
+ except (ValueError, KeyError, TypeError, AttributeError) as e:
117
124
  msg = f"Error getting model names: {e}"
118
125
  raise ValueError(msg) from e
119
126
  return build_config
@@ -1,5 +1,8 @@
1
+ """Helpers module - backwards compatibility for moved components."""
2
+
1
3
  from __future__ import annotations
2
4
 
5
+ import sys
3
6
  from typing import TYPE_CHECKING, Any
4
7
 
5
8
  from lfx.components._importing import import_mod
@@ -33,12 +36,111 @@ __all__ = [
33
36
  "OutputParserComponent",
34
37
  ]
35
38
 
39
+ # Register redirected submodules in sys.modules for direct importlib.import_module() calls
40
+ # This allows imports like: import lfx.components.helpers.current_date
41
+ _redirected_submodules = {
42
+ "lfx.components.helpers.current_date": "lfx.components.utilities.current_date",
43
+ "lfx.components.helpers.calculator_core": "lfx.components.utilities.calculator_core",
44
+ "lfx.components.helpers.id_generator": "lfx.components.utilities.id_generator",
45
+ "lfx.components.helpers.memory": "lfx.components.models_and_agents.memory",
46
+ }
47
+
48
+ for old_path, new_path in _redirected_submodules.items():
49
+ if old_path not in sys.modules:
50
+ # Use a lazy loader that imports the actual module when accessed
51
+ class _RedirectedModule:
52
+ def __init__(self, target_path: str, original_path: str):
53
+ self._target_path = target_path
54
+ self._original_path = original_path
55
+ self._module = None
56
+
57
+ def __getattr__(self, name: str) -> Any:
58
+ if self._module is None:
59
+ from importlib import import_module
60
+
61
+ self._module = import_module(self._target_path)
62
+ # Also register under the original path for future imports
63
+ sys.modules[self._original_path] = self._module
64
+ return getattr(self._module, name)
65
+
66
+ def __repr__(self) -> str:
67
+ return f"<redirected module '{self._original_path}' -> '{self._target_path}'>"
68
+
69
+ sys.modules[old_path] = _RedirectedModule(new_path, old_path)
70
+
36
71
 
37
72
  def __getattr__(attr_name: str) -> Any:
38
73
  """Lazily import helper components on attribute access."""
74
+ # Handle submodule access for backwards compatibility
75
+ # e.g., lfx.components.helpers.id_generator -> lfx.components.utilities.id_generator
76
+ if attr_name == "id_generator":
77
+ from importlib import import_module
78
+
79
+ result = import_module("lfx.components.utilities.id_generator")
80
+ globals()[attr_name] = result
81
+ return result
82
+ if attr_name == "calculator_core":
83
+ from importlib import import_module
84
+
85
+ result = import_module("lfx.components.utilities.calculator_core")
86
+ globals()[attr_name] = result
87
+ return result
88
+ if attr_name == "current_date":
89
+ from importlib import import_module
90
+
91
+ result = import_module("lfx.components.utilities.current_date")
92
+ globals()[attr_name] = result
93
+ return result
94
+ if attr_name == "memory":
95
+ from importlib import import_module
96
+
97
+ result = import_module("lfx.components.models_and_agents.memory")
98
+ globals()[attr_name] = result
99
+ return result
100
+
39
101
  if attr_name not in _dynamic_imports:
40
102
  msg = f"module '{__name__}' has no attribute '{attr_name}'"
41
103
  raise AttributeError(msg)
104
+
105
+ # CurrentDateComponent, CalculatorComponent, and IDGeneratorComponent were moved to utilities
106
+ # Forward them to utilities for backwards compatibility
107
+ if attr_name in ("CurrentDateComponent", "CalculatorComponent", "IDGeneratorComponent"):
108
+ from lfx.components import utilities
109
+
110
+ result = getattr(utilities, attr_name)
111
+ globals()[attr_name] = result
112
+ return result
113
+
114
+ # MemoryComponent was moved to models_and_agents
115
+ # Forward it to models_and_agents for backwards compatibility
116
+ if attr_name == "MemoryComponent":
117
+ from lfx.components import models_and_agents
118
+
119
+ result = getattr(models_and_agents, attr_name)
120
+ globals()[attr_name] = result
121
+ return result
122
+
123
+ # CreateListComponent, MessageStoreComponent, and OutputParserComponent were moved to processing
124
+ # Forward them to processing for backwards compatibility
125
+ if attr_name == "CreateListComponent":
126
+ from lfx.components import processing
127
+
128
+ result = getattr(processing, attr_name)
129
+ globals()[attr_name] = result
130
+ return result
131
+ if attr_name == "MessageStoreComponent":
132
+ from lfx.components import processing
133
+
134
+ result = processing.MessageStoreComponent
135
+ globals()[attr_name] = result
136
+ return result
137
+ if attr_name == "OutputParserComponent":
138
+ from lfx.components import processing
139
+
140
+ result = getattr(processing, attr_name)
141
+ globals()[attr_name] = result
142
+ return result
143
+
42
144
  try:
43
145
  result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent)
44
146
  except (ModuleNotFoundError, ImportError, AttributeError) as e:
@@ -197,8 +197,14 @@ class WatsonxAIComponent(LCModelComponent):
197
197
  "logit_bias": logit_bias,
198
198
  }
199
199
 
200
+ # Pass API key as plain string to avoid SecretStr serialization issues
201
+ # when model is configured with with_config() or used in batch operations
202
+ api_key_value = self.api_key
203
+ if isinstance(api_key_value, SecretStr):
204
+ api_key_value = api_key_value.get_secret_value()
205
+
200
206
  return ChatWatsonx(
201
- apikey=SecretStr(self.api_key).get_secret_value(),
207
+ apikey=api_key_value,
202
208
  url=self.base_url,
203
209
  project_id=self.project_id,
204
210
  model_id=self.model_name,
@@ -9,15 +9,17 @@ if TYPE_CHECKING:
9
9
  from lfx.components.input_output.chat_output import ChatOutput
10
10
  from lfx.components.input_output.text import TextInputComponent
11
11
  from lfx.components.input_output.text_output import TextOutputComponent
12
+ from lfx.components.input_output.webhook import WebhookComponent
12
13
 
13
14
  _dynamic_imports = {
14
15
  "ChatInput": "chat",
15
16
  "ChatOutput": "chat_output",
16
17
  "TextInputComponent": "text",
17
18
  "TextOutputComponent": "text_output",
19
+ "WebhookComponent": "webhook",
18
20
  }
19
21
 
20
- __all__ = ["ChatInput", "ChatOutput", "TextInputComponent", "TextOutputComponent"]
22
+ __all__ = ["ChatInput", "ChatOutput", "TextInputComponent", "TextOutputComponent", "WebhookComponent"]
21
23
 
22
24
 
23
25
  def __getattr__(attr_name: str) -> Any:
@@ -19,7 +19,7 @@ from lfx.utils.constants import (
19
19
  class ChatInput(ChatComponent):
20
20
  display_name = "Chat Input"
21
21
  description = "Get chat inputs from the Playground."
22
- documentation: str = "https://docs.langflow.org/components-io#chat-input"
22
+ documentation: str = "https://docs.langflow.org/chat-input-and-output"
23
23
  icon = "MessagesSquare"
24
24
  name = "ChatInput"
25
25
  minimized = True
@@ -89,15 +89,16 @@ class ChatInput(ChatComponent):
89
89
  # Filter out None/empty values
90
90
  files = [f for f in files if f is not None and f != ""]
91
91
 
92
+ session_id = self.session_id or self.graph.session_id or ""
92
93
  message = await Message.create(
93
94
  text=self.input_value,
94
95
  sender=self.sender,
95
96
  sender_name=self.sender_name,
96
- session_id=self.session_id,
97
+ session_id=session_id,
97
98
  context_id=self.context_id,
98
99
  files=files,
99
100
  )
100
- if self.session_id and isinstance(message, Message) and self.should_store_message:
101
+ if session_id and isinstance(message, Message) and self.should_store_message:
101
102
  stored_message = await self.send_message(
102
103
  message,
103
104
  )
@@ -22,7 +22,7 @@ from lfx.utils.constants import (
22
22
  class ChatOutput(ChatComponent):
23
23
  display_name = "Chat Output"
24
24
  description = "Display a chat message in the Playground."
25
- documentation: str = "https://docs.langflow.org/components-io#chat-output"
25
+ documentation: str = "https://docs.langflow.org/chat-input-and-output"
26
26
  icon = "MessagesSquare"
27
27
  name = "ChatOutput"
28
28
  minimized = True
@@ -117,23 +117,29 @@ class ChatOutput(ChatComponent):
117
117
  source, _, display_name, source_id = self.get_properties_from_source_component()
118
118
 
119
119
  # Create or use existing Message object
120
- if isinstance(self.input_value, Message):
120
+ if isinstance(self.input_value, Message) and not self.is_connected_to_chat_input():
121
121
  message = self.input_value
122
122
  # Update message properties
123
123
  message.text = text
124
+ # Preserve existing session_id from the incoming message if it exists
125
+ existing_session_id = message.session_id
124
126
  else:
125
127
  message = Message(text=text)
128
+ existing_session_id = None
126
129
 
127
130
  # Set message properties
128
131
  message.sender = self.sender
129
132
  message.sender_name = self.sender_name
130
- message.session_id = self.session_id
133
+ # Preserve session_id from incoming message, or use component/graph session_id
134
+ message.session_id = (
135
+ self.session_id or existing_session_id or (self.graph.session_id if hasattr(self, "graph") else None) or ""
136
+ )
131
137
  message.context_id = self.context_id
132
138
  message.flow_id = self.graph.flow_id if hasattr(self, "graph") else None
133
139
  message.properties.source = self._build_source(source_id, display_name, source)
134
140
 
135
141
  # Store message if needed
136
- if self.session_id and self.should_store_message:
142
+ if message.session_id and self.should_store_message:
137
143
  stored_message = await self.send_message(message)
138
144
  self.message.value = stored_message
139
145
  message = stored_message
@@ -6,7 +6,7 @@ from lfx.schema.message import Message
6
6
  class TextInputComponent(TextComponent):
7
7
  display_name = "Text Input"
8
8
  description = "Get user text inputs."
9
- documentation: str = "https://docs.langflow.org/components-io#text-input"
9
+ documentation: str = "https://docs.langflow.org/text-input-and-output"
10
10
  icon = "type"
11
11
  name = "TextInput"
12
12
 
@@ -6,7 +6,7 @@ from lfx.schema.message import Message
6
6
  class TextOutputComponent(TextComponent):
7
7
  display_name = "Text Output"
8
8
  description = "Sends text output via API."
9
- documentation: str = "https://docs.langflow.org/components-io#text-output"
9
+ documentation: str = "https://docs.langflow.org/text-input-and-output"
10
10
  icon = "type"
11
11
  name = "TextOutput"
12
12
 
@@ -7,7 +7,7 @@ from lfx.schema.data import Data
7
7
 
8
8
  class WebhookComponent(Component):
9
9
  display_name = "Webhook"
10
- documentation: str = "https://docs.langflow.org/components-data#webhook"
10
+ documentation: str = "https://docs.langflow.org/component-webhook"
11
11
  name = "Webhook"
12
12
  icon = "webhook"
13
13
 
@@ -1,12 +1,19 @@
1
+ """Knowledge bases module - backwards compatibility alias for files_and_knowledge.
2
+
3
+ This module provides backwards compatibility by forwarding all imports
4
+ to files_and_knowledge where the actual knowledge base components are located.
5
+ """
6
+
1
7
  from __future__ import annotations
2
8
 
9
+ import sys
3
10
  from typing import TYPE_CHECKING, Any
4
11
 
5
12
  from lfx.components._importing import import_mod
6
13
 
7
14
  if TYPE_CHECKING:
8
- from lfx.components.knowledge_bases.ingestion import KnowledgeIngestionComponent
9
- from lfx.components.knowledge_bases.retrieval import KnowledgeRetrievalComponent
15
+ from lfx.components.files_and_knowledge.ingestion import KnowledgeIngestionComponent
16
+ from lfx.components.files_and_knowledge.retrieval import KnowledgeRetrievalComponent
10
17
 
11
18
  _dynamic_imports = {
12
19
  "KnowledgeIngestionComponent": "ingestion",
@@ -15,14 +22,61 @@ _dynamic_imports = {
15
22
 
16
23
  __all__ = ["KnowledgeIngestionComponent", "KnowledgeRetrievalComponent"]
17
24
 
25
+ # Register redirected submodules in sys.modules for direct importlib.import_module() calls
26
+ # This allows imports like: import lfx.components.knowledge_bases.ingestion
27
+ _redirected_submodules = {
28
+ "lfx.components.knowledge_bases.ingestion": "lfx.components.files_and_knowledge.ingestion",
29
+ "lfx.components.knowledge_bases.retrieval": "lfx.components.files_and_knowledge.retrieval",
30
+ }
31
+
32
+ for old_path, new_path in _redirected_submodules.items():
33
+ if old_path not in sys.modules:
34
+ # Use a lazy loader that imports the actual module when accessed
35
+ class _RedirectedModule:
36
+ def __init__(self, target_path: str, original_path: str):
37
+ self._target_path = target_path
38
+ self._original_path = original_path
39
+ self._module = None
40
+
41
+ def __getattr__(self, name: str) -> Any:
42
+ if self._module is None:
43
+ from importlib import import_module
44
+
45
+ self._module = import_module(self._target_path)
46
+ # Also register under the original path for future imports
47
+ sys.modules[self._original_path] = self._module
48
+ return getattr(self._module, name)
49
+
50
+ def __repr__(self) -> str:
51
+ return f"<redirected module '{self._original_path}' -> '{self._target_path}'>"
52
+
53
+ sys.modules[old_path] = _RedirectedModule(new_path, old_path)
54
+
18
55
 
19
56
  def __getattr__(attr_name: str) -> Any:
20
- """Lazily import input/output components on attribute access."""
57
+ """Forward attribute access to files_and_knowledge components."""
58
+ # Handle submodule access for backwards compatibility
59
+ if attr_name == "ingestion":
60
+ from importlib import import_module
61
+
62
+ result = import_module("lfx.components.files_and_knowledge.ingestion")
63
+ globals()[attr_name] = result
64
+ return result
65
+ if attr_name == "retrieval":
66
+ from importlib import import_module
67
+
68
+ result = import_module("lfx.components.files_and_knowledge.retrieval")
69
+ globals()[attr_name] = result
70
+ return result
71
+
21
72
  if attr_name not in _dynamic_imports:
22
73
  msg = f"module '{__name__}' has no attribute '{attr_name}'"
23
74
  raise AttributeError(msg)
75
+
76
+ # Import from files_and_knowledge using the correct package path
77
+ package = "lfx.components.files_and_knowledge"
24
78
  try:
25
- result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent)
79
+ result = import_mod(attr_name, _dynamic_imports[attr_name], package)
26
80
  except (ModuleNotFoundError, ImportError, AttributeError) as e:
27
81
  msg = f"Could not import '{attr_name}' from '{__name__}': {e}"
28
82
  raise AttributeError(msg) from e
@@ -31,4 +85,5 @@ def __getattr__(attr_name: str) -> Any:
31
85
 
32
86
 
33
87
  def __dir__() -> list[str]:
88
+ """Return directory of available components."""
34
89
  return list(__all__)
@@ -10,7 +10,7 @@ from lfx.utils.util import unescape_string
10
10
  class CharacterTextSplitterComponent(LCTextSplitterComponent):
11
11
  display_name = "Character Text Splitter"
12
12
  description = "Split text by number of characters."
13
- documentation = "https://docs.langflow.org/components/text-splitters#charactertextsplitter"
13
+ documentation = "https://docs.langflow.org/bundles-langchain"
14
14
  name = "CharacterTextSplitter"
15
15
  icon = "LangChain"
16
16