lfx-nightly 0.1.13.dev0__py3-none-any.whl → 0.2.0.dev0__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 (86) hide show
  1. lfx/_assets/component_index.json +1 -1
  2. lfx/base/agents/agent.py +109 -29
  3. lfx/base/agents/events.py +102 -35
  4. lfx/base/agents/utils.py +15 -2
  5. lfx/base/composio/composio_base.py +24 -9
  6. lfx/base/datastax/__init__.py +5 -0
  7. lfx/{components/vectorstores/astradb.py → base/datastax/astradb_base.py} +84 -473
  8. lfx/base/io/chat.py +5 -4
  9. lfx/base/mcp/util.py +101 -15
  10. lfx/base/models/model_input_constants.py +74 -7
  11. lfx/base/models/ollama_constants.py +3 -0
  12. lfx/base/models/watsonx_constants.py +12 -0
  13. lfx/cli/commands.py +1 -1
  14. lfx/components/agents/__init__.py +3 -1
  15. lfx/components/agents/agent.py +47 -4
  16. lfx/components/agents/altk_agent.py +366 -0
  17. lfx/components/agents/cuga_agent.py +1 -1
  18. lfx/components/agents/mcp_component.py +32 -2
  19. lfx/components/amazon/amazon_bedrock_converse.py +1 -1
  20. lfx/components/apify/apify_actor.py +3 -3
  21. lfx/components/datastax/__init__.py +12 -6
  22. lfx/components/datastax/{astra_assistant_manager.py → astradb_assistant_manager.py} +1 -0
  23. lfx/components/datastax/astradb_chatmemory.py +40 -0
  24. lfx/components/datastax/astradb_cql.py +5 -31
  25. lfx/components/datastax/astradb_graph.py +9 -123
  26. lfx/components/datastax/astradb_tool.py +12 -52
  27. lfx/components/datastax/astradb_vectorstore.py +133 -976
  28. lfx/components/datastax/create_assistant.py +1 -0
  29. lfx/components/datastax/create_thread.py +1 -0
  30. lfx/components/datastax/dotenv.py +1 -0
  31. lfx/components/datastax/get_assistant.py +1 -0
  32. lfx/components/datastax/getenvvar.py +1 -0
  33. lfx/components/datastax/graph_rag.py +1 -1
  34. lfx/components/datastax/list_assistants.py +1 -0
  35. lfx/components/datastax/run.py +1 -0
  36. lfx/components/docling/__init__.py +3 -0
  37. lfx/components/docling/docling_remote_vlm.py +284 -0
  38. lfx/components/ibm/watsonx.py +25 -21
  39. lfx/components/input_output/chat.py +8 -0
  40. lfx/components/input_output/chat_output.py +8 -0
  41. lfx/components/knowledge_bases/ingestion.py +17 -9
  42. lfx/components/knowledge_bases/retrieval.py +16 -8
  43. lfx/components/logic/loop.py +4 -0
  44. lfx/components/mistral/mistral_embeddings.py +1 -1
  45. lfx/components/models/embedding_model.py +88 -7
  46. lfx/components/ollama/ollama.py +221 -14
  47. lfx/components/openrouter/openrouter.py +49 -147
  48. lfx/components/processing/parser.py +6 -1
  49. lfx/components/processing/structured_output.py +55 -17
  50. lfx/components/vectorstores/__init__.py +0 -6
  51. lfx/custom/custom_component/component.py +3 -2
  52. lfx/field_typing/constants.py +1 -0
  53. lfx/graph/edge/base.py +2 -2
  54. lfx/graph/graph/base.py +1 -1
  55. lfx/graph/graph/schema.py +3 -2
  56. lfx/graph/vertex/vertex_types.py +1 -1
  57. lfx/io/schema.py +6 -0
  58. lfx/schema/schema.py +5 -0
  59. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/METADATA +1 -1
  60. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/RECORD +63 -81
  61. lfx/components/datastax/astra_db.py +0 -77
  62. lfx/components/datastax/cassandra.py +0 -92
  63. lfx/components/vectorstores/astradb_graph.py +0 -326
  64. lfx/components/vectorstores/cassandra.py +0 -264
  65. lfx/components/vectorstores/cassandra_graph.py +0 -238
  66. lfx/components/vectorstores/chroma.py +0 -167
  67. lfx/components/vectorstores/clickhouse.py +0 -135
  68. lfx/components/vectorstores/couchbase.py +0 -102
  69. lfx/components/vectorstores/elasticsearch.py +0 -267
  70. lfx/components/vectorstores/faiss.py +0 -111
  71. lfx/components/vectorstores/graph_rag.py +0 -141
  72. lfx/components/vectorstores/hcd.py +0 -314
  73. lfx/components/vectorstores/milvus.py +0 -115
  74. lfx/components/vectorstores/mongodb_atlas.py +0 -213
  75. lfx/components/vectorstores/opensearch.py +0 -243
  76. lfx/components/vectorstores/pgvector.py +0 -72
  77. lfx/components/vectorstores/pinecone.py +0 -134
  78. lfx/components/vectorstores/qdrant.py +0 -109
  79. lfx/components/vectorstores/supabase.py +0 -76
  80. lfx/components/vectorstores/upstash.py +0 -124
  81. lfx/components/vectorstores/vectara.py +0 -97
  82. lfx/components/vectorstores/vectara_rag.py +0 -164
  83. lfx/components/vectorstores/weaviate.py +0 -89
  84. /lfx/components/datastax/{astra_vectorize.py → astradb_vectorize.py} +0 -0
  85. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/WHEEL +0 -0
  86. {lfx_nightly-0.1.13.dev0.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/entry_points.txt +0 -0
@@ -10,6 +10,7 @@ class AssistantsCreateAssistant(ComponentWithCache):
10
10
  icon = "AstraDB"
11
11
  display_name = "Create Assistant"
12
12
  description = "Creates an Assistant and returns it's id"
13
+ legacy = True
13
14
 
14
15
  inputs = [
15
16
  StrInput(
@@ -9,6 +9,7 @@ class AssistantsCreateThread(ComponentWithCache):
9
9
  display_name = "Create Assistant Thread"
10
10
  description = "Creates a thread and returns the thread id"
11
11
  icon = "AstraDB"
12
+ legacy = True
12
13
  inputs = [
13
14
  MultilineInput(
14
15
  name="env_set",
@@ -12,6 +12,7 @@ class Dotenv(Component):
12
12
  display_name = "Dotenv"
13
13
  description = "Load .env file into env vars"
14
14
  icon = "AstraDB"
15
+ legacy = True
15
16
  inputs = [
16
17
  MultilineSecretInput(
17
18
  name="dotenv_file_content",
@@ -9,6 +9,7 @@ class AssistantsGetAssistantName(ComponentWithCache):
9
9
  display_name = "Get Assistant name"
10
10
  description = "Assistant by id"
11
11
  icon = "AstraDB"
12
+ legacy = True
12
13
  inputs = [
13
14
  StrInput(
14
15
  name="assistant_id",
@@ -10,6 +10,7 @@ class GetEnvVar(Component):
10
10
  display_name = "Get Environment Variable"
11
11
  description = "Gets the value of an environment variable from the system."
12
12
  icon = "AstraDB"
13
+ legacy = True
13
14
 
14
15
  inputs = [
15
16
  StrInput(
@@ -44,7 +44,7 @@ class GraphRAGComponent(LCVectorStoreComponent):
44
44
 
45
45
  display_name: str = "Graph RAG"
46
46
  description: str = "Graph RAG traversal for vector store."
47
- name = "Graph RAG"
47
+ name = "GraphRAG"
48
48
  icon: str = "AstraDB"
49
49
 
50
50
  inputs = [
@@ -8,6 +8,7 @@ class AssistantsListAssistants(ComponentWithCache):
8
8
  display_name = "List Assistants"
9
9
  description = "Returns a list of assistant id's"
10
10
  icon = "AstraDB"
11
+ legacy = True
11
12
  outputs = [
12
13
  Output(display_name="Assistants", name="assistants", method="process_inputs"),
13
14
  ]
@@ -14,6 +14,7 @@ class AssistantsRun(ComponentWithCache):
14
14
  display_name = "Run Assistant"
15
15
  description = "Executes an Assistant Run against a thread"
16
16
  icon = "AstraDB"
17
+ legacy = True
17
18
 
18
19
  def __init__(self, **kwargs) -> None:
19
20
  super().__init__(**kwargs)
@@ -8,6 +8,7 @@ if TYPE_CHECKING:
8
8
  from .chunk_docling_document import ChunkDoclingDocumentComponent
9
9
  from .docling_inline import DoclingInlineComponent
10
10
  from .docling_remote import DoclingRemoteComponent
11
+ from .docling_remote_vlm import DoclingRemoteVLMComponent
11
12
  from .export_docling_document import ExportDoclingDocumentComponent
12
13
 
13
14
  _dynamic_imports = {
@@ -15,12 +16,14 @@ _dynamic_imports = {
15
16
  "DoclingInlineComponent": "docling_inline",
16
17
  "DoclingRemoteComponent": "docling_remote",
17
18
  "ExportDoclingDocumentComponent": "export_docling_document",
19
+ "DoclingRemoteVLMComponent": "docling_remote_vlm",
18
20
  }
19
21
 
20
22
  __all__ = [
21
23
  "ChunkDoclingDocumentComponent",
22
24
  "DoclingInlineComponent",
23
25
  "DoclingRemoteComponent",
26
+ "DoclingRemoteVLMComponent",
24
27
  "ExportDoclingDocumentComponent",
25
28
  ]
26
29
 
@@ -0,0 +1,284 @@
1
+ from typing import Any
2
+
3
+ import requests
4
+ from docling.datamodel.base_models import ConversionStatus, InputFormat
5
+ from docling.datamodel.pipeline_options import (
6
+ ApiVlmOptions,
7
+ ResponseFormat,
8
+ VlmPipelineOptions,
9
+ )
10
+ from docling.document_converter import DocumentConverter, PdfFormatOption
11
+ from docling.pipeline.vlm_pipeline import VlmPipeline
12
+ from langflow.base.data import BaseFileComponent
13
+ from langflow.inputs import DropdownInput, SecretStrInput, StrInput
14
+ from langflow.schema import Data
15
+ from langflow.schema.dotdict import dotdict
16
+
17
+ from lfx.components.ibm.watsonx import WatsonxAIComponent
18
+ from lfx.log.logger import logger
19
+
20
+
21
+ class DoclingRemoteVLMComponent(BaseFileComponent):
22
+ display_name = "Docling Remote VLM"
23
+ description = (
24
+ "Uses Docling to process input documents running a VLM pipeline with a remote model"
25
+ "(OpenAI-compatible API or IBM Cloud)."
26
+ )
27
+ documentation = "https://docling-project.github.io/docling/examples/vlm_pipeline_api_model/"
28
+ trace_type = "tool"
29
+ icon = "Docling"
30
+ name = "DoclingRemoteVLM"
31
+
32
+ # https://docling-project.github.io/docling/usage/supported_formats/
33
+ VALID_EXTENSIONS = [
34
+ "adoc",
35
+ "asciidoc",
36
+ "asc",
37
+ "bmp",
38
+ "csv",
39
+ "dotx",
40
+ "dotm",
41
+ "docm",
42
+ "docx",
43
+ "htm",
44
+ "html",
45
+ "jpeg",
46
+ "json",
47
+ "md",
48
+ "pdf",
49
+ "png",
50
+ "potx",
51
+ "ppsx",
52
+ "pptm",
53
+ "potm",
54
+ "ppsm",
55
+ "pptx",
56
+ "tiff",
57
+ "txt",
58
+ "xls",
59
+ "xlsx",
60
+ "xhtml",
61
+ "xml",
62
+ "webp",
63
+ ]
64
+
65
+ inputs = [
66
+ *BaseFileComponent.get_base_inputs(),
67
+ DropdownInput(
68
+ name="provider",
69
+ display_name="Provider",
70
+ info="Select which remote VLM provider to use.",
71
+ options=["IBM Cloud", "OpenAI-Compatible"],
72
+ value="IBM Cloud",
73
+ real_time_refresh=True,
74
+ ),
75
+ # IBM Cloud inputs
76
+ SecretStrInput(
77
+ name="watsonx_api_key",
78
+ display_name="Watsonx API Key",
79
+ info="IBM Cloud API key used for authentication (leave blank to load from .env).",
80
+ required=False,
81
+ ),
82
+ StrInput(
83
+ name="watsonx_project_id",
84
+ display_name="Watsonx Project ID",
85
+ required=False,
86
+ info="The Watsonx project ID or deployment space ID associated with the model.",
87
+ value="",
88
+ ),
89
+ DropdownInput(
90
+ name="url",
91
+ display_name="Watsonx API Endpoint",
92
+ info="The base URL of the Watsonx API.",
93
+ options=[
94
+ "https://us-south.ml.cloud.ibm.com",
95
+ "https://eu-de.ml.cloud.ibm.com",
96
+ "https://eu-gb.ml.cloud.ibm.com",
97
+ "https://au-syd.ml.cloud.ibm.com",
98
+ "https://jp-tok.ml.cloud.ibm.com",
99
+ "https://ca-tor.ml.cloud.ibm.com",
100
+ ],
101
+ real_time_refresh=True,
102
+ ),
103
+ DropdownInput(
104
+ name="model_name",
105
+ display_name="Model Name",
106
+ options=[],
107
+ value=None,
108
+ dynamic=True,
109
+ required=False,
110
+ ),
111
+ # OpenAI inputs
112
+ StrInput(
113
+ name="openai_base_url",
114
+ display_name="OpenAI-Compatible API Base URL",
115
+ info="Example: https://openrouter.ai/api/",
116
+ required=False,
117
+ show=False,
118
+ ),
119
+ SecretStrInput(
120
+ name="openai_api_key",
121
+ display_name="API Key",
122
+ info="API key for OpenAI-compatible endpoints (leave blank if not required).",
123
+ required=False,
124
+ show=False,
125
+ ),
126
+ StrInput(
127
+ name="openai_model",
128
+ display_name="OpenAI Model Name",
129
+ info="Model ID for OpenAI-compatible provider (e.g. gpt-4o-mini).",
130
+ required=False,
131
+ show=False,
132
+ ),
133
+ StrInput(name="vlm_prompt", display_name="Prompt", info="Prompt for VLM.", required=False),
134
+ ]
135
+
136
+ outputs = [*BaseFileComponent.get_base_outputs()]
137
+
138
+ @staticmethod
139
+ def fetch_models(base_url: str) -> list[str]:
140
+ """Fetch available models from the Watsonx.ai API."""
141
+ try:
142
+ endpoint = f"{base_url}/ml/v1/foundation_model_specs"
143
+ params = {"version": "2024-09-16", "filters": "function_text_chat,!lifecycle_withdrawn"}
144
+ response = requests.get(endpoint, params=params, timeout=10)
145
+ response.raise_for_status()
146
+ data = response.json()
147
+ models = [model["model_id"] for model in data.get("resources", [])]
148
+ return sorted(models)
149
+ except (requests.RequestException, requests.HTTPError, requests.Timeout, ConnectionError, ValueError):
150
+ logger.exception("Error fetching models. Using default models.")
151
+ return WatsonxAIComponent._default_models # noqa: SLF001
152
+
153
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
154
+ """Update shown fields based on chosen provider."""
155
+ logger.info(f"update_build_config called: field_name={field_name}, field_value={field_value}")
156
+
157
+ if field_name == "provider":
158
+ provider_choice = field_value
159
+
160
+ if provider_choice == "IBM Cloud":
161
+ build_config.model_name.show = True
162
+ build_config.watsonx_api_key.show = True
163
+ build_config.watsonx_project_id.show = True
164
+ build_config.url.show = True
165
+
166
+ build_config.openai_base_url.show = False
167
+ build_config.openai_api_key.show = False
168
+ build_config.openai_model.show = False
169
+
170
+ elif provider_choice == "OpenAI-Compatible":
171
+ build_config.model_name.show = False
172
+ build_config.watsonx_api_key.show = False
173
+ build_config.watsonx_project_id.show = False
174
+ build_config.url.show = False
175
+
176
+ build_config.openai_base_url.show = True
177
+ build_config.openai_api_key.show = True
178
+ build_config.openai_model.show = True
179
+
180
+ if field_name == "url":
181
+ provider_value = build_config.provider.value if hasattr(build_config, "provider") else None
182
+ if provider_value == "IBM Cloud" and field_value:
183
+ models = self.fetch_models(base_url=field_value)
184
+ build_config.model_name.options = models
185
+ if models:
186
+ build_config.model_name.value = models[0]
187
+ logger.info(f"Updated Watsonx model list: {len(models)} models found.")
188
+
189
+ def watsonx_vlm_options(self, model: str, prompt: str):
190
+ """Creates Docling ApiVlmOptions for a watsonx VLM."""
191
+ api_key = getattr(self, "watsonx_api_key", "")
192
+ project_id = getattr(self, "watsonx_project_id", "")
193
+ base_url = getattr(self, "url", "https://us-south.ml.cloud.ibm.com")
194
+
195
+ def _get_iam_access_token(api_key: str) -> str:
196
+ res = requests.post(
197
+ url="https://iam.cloud.ibm.com/identity/token",
198
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
199
+ data=f"grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={api_key}",
200
+ timeout=90,
201
+ )
202
+ res.raise_for_status()
203
+ return res.json()["access_token"]
204
+
205
+ access_token = _get_iam_access_token(api_key)
206
+ return ApiVlmOptions(
207
+ url=f"{base_url}/ml/v1/text/chat?version=2023-05-29",
208
+ params={"model_id": model, "project_id": project_id, "parameters": {"max_new_tokens": 400}},
209
+ headers={"Authorization": f"Bearer {access_token}"},
210
+ prompt=prompt,
211
+ timeout=60,
212
+ response_format=ResponseFormat.MARKDOWN,
213
+ )
214
+
215
+ def openai_compatible_vlm_options(
216
+ self,
217
+ model: str,
218
+ prompt: str,
219
+ response_format: ResponseFormat,
220
+ url: str,
221
+ temperature: float = 0.7,
222
+ max_tokens: int = 4096,
223
+ api_key: str = "",
224
+ *,
225
+ skip_special_tokens: bool = False,
226
+ ):
227
+ """Create OpenAI-compatible Docling ApiVlmOptions options (e.g., LM Studio, vLLM, Ollama)."""
228
+ api_key = getattr(self, "openai_api_key", api_key)
229
+ model_override = getattr(self, "openai_model", model)
230
+
231
+ headers = {}
232
+ if api_key:
233
+ headers["Authorization"] = f"Bearer {api_key}"
234
+
235
+ return ApiVlmOptions(
236
+ url=f"{url}/v1/chat/completions",
237
+ params={"model": model_override, "max_tokens": max_tokens, "skip_special_tokens": skip_special_tokens},
238
+ headers=headers,
239
+ prompt=prompt,
240
+ timeout=90,
241
+ scale=2.0,
242
+ temperature=temperature,
243
+ response_format=response_format,
244
+ )
245
+
246
+ def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[BaseFileComponent.BaseFile]:
247
+ file_paths = [file.path for file in file_list if file.path]
248
+ if not file_paths:
249
+ logger.warning("No files to process.")
250
+ return file_list
251
+
252
+ provider = getattr(self, "provider", "IBM Cloud")
253
+ prompt = getattr(self, "vlm_prompt", "")
254
+
255
+ if provider == "IBM Cloud":
256
+ model = getattr(self, "model_name", "")
257
+ vlm_opts = self.watsonx_vlm_options(model=model, prompt=prompt)
258
+ else:
259
+ model = getattr(self, "openai_model", "") or getattr(self, "model_name", "")
260
+ base_url = getattr(self, "openai_base_url", "")
261
+ vlm_opts = self.openai_compatible_vlm_options(
262
+ model=model,
263
+ prompt=prompt,
264
+ response_format=ResponseFormat.MARKDOWN,
265
+ url=base_url,
266
+ )
267
+
268
+ pipeline_options = VlmPipelineOptions(enable_remote_services=True)
269
+ pipeline_options.vlm_options = vlm_opts
270
+
271
+ converter = DocumentConverter(
272
+ format_options={
273
+ InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options, pipeline_cls=VlmPipeline)
274
+ }
275
+ )
276
+
277
+ results = converter.convert_all(file_paths)
278
+ processed_data = [
279
+ Data(data={"doc": res.document, "file_path": str(res.input.file)})
280
+ if res.status == ConversionStatus.SUCCESS
281
+ else None
282
+ for res in results
283
+ ]
284
+ return self.rollup_data(file_list, processed_data)
@@ -21,23 +21,24 @@ class WatsonxAIComponent(LCModelComponent):
21
21
  beta = False
22
22
 
23
23
  _default_models = ["ibm/granite-3-2b-instruct", "ibm/granite-3-8b-instruct", "ibm/granite-13b-instruct-v2"]
24
-
24
+ _urls = [
25
+ "https://us-south.ml.cloud.ibm.com",
26
+ "https://eu-de.ml.cloud.ibm.com",
27
+ "https://eu-gb.ml.cloud.ibm.com",
28
+ "https://au-syd.ml.cloud.ibm.com",
29
+ "https://jp-tok.ml.cloud.ibm.com",
30
+ "https://ca-tor.ml.cloud.ibm.com",
31
+ ]
25
32
  inputs = [
26
33
  *LCModelComponent.get_base_inputs(),
27
34
  DropdownInput(
28
- name="url",
35
+ name="base_url",
29
36
  display_name="watsonx API Endpoint",
30
37
  info="The base URL of the API.",
31
- value=None,
32
- options=[
33
- "https://us-south.ml.cloud.ibm.com",
34
- "https://eu-de.ml.cloud.ibm.com",
35
- "https://eu-gb.ml.cloud.ibm.com",
36
- "https://au-syd.ml.cloud.ibm.com",
37
- "https://jp-tok.ml.cloud.ibm.com",
38
- "https://ca-tor.ml.cloud.ibm.com",
39
- ],
38
+ value=[],
39
+ options=_urls,
40
40
  real_time_refresh=True,
41
+ required=True,
41
42
  ),
42
43
  StrInput(
43
44
  name="project_id",
@@ -56,8 +57,9 @@ class WatsonxAIComponent(LCModelComponent):
56
57
  display_name="Model Name",
57
58
  options=[],
58
59
  value=None,
59
- dynamic=True,
60
+ real_time_refresh=True,
60
61
  required=True,
62
+ refresh_button=True,
61
63
  ),
62
64
  IntInput(
63
65
  name="max_tokens",
@@ -155,18 +157,20 @@ class WatsonxAIComponent(LCModelComponent):
155
157
 
156
158
  def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
157
159
  """Update model options when URL or API key changes."""
158
- logger.info("Updating build config. Field name: %s, Field value: %s", field_name, field_value)
159
-
160
- if field_name == "url" and field_value:
160
+ if field_name == "base_url" and field_value:
161
161
  try:
162
- models = self.fetch_models(base_url=build_config.url.value)
163
- build_config.model_name.options = models
164
- if build_config.model_name.value:
165
- build_config.model_name.value = models[0]
166
- info_message = f"Updated model options: {len(models)} models found in {build_config.url.value}"
162
+ models = self.fetch_models(base_url=field_value)
163
+ build_config["model_name"]["options"] = models
164
+ if build_config["model_name"]["value"]:
165
+ build_config["model_name"]["value"] = models[0]
166
+ info_message = f"Updated model options: {len(models)} models found in {field_value}"
167
167
  logger.info(info_message)
168
168
  except Exception: # noqa: BLE001
169
169
  logger.exception("Error updating model options.")
170
+ if field_name == "model_name" and field_value and field_value in WatsonxAIComponent._urls:
171
+ build_config["model_name"]["options"] = self.fetch_models(base_url=field_value)
172
+ build_config["model_name"]["value"] = ""
173
+ return build_config
170
174
 
171
175
  def build_model(self) -> LanguageModel:
172
176
  # Parse logit_bias from JSON string if provided
@@ -195,7 +199,7 @@ class WatsonxAIComponent(LCModelComponent):
195
199
 
196
200
  return ChatWatsonx(
197
201
  apikey=SecretStr(self.api_key).get_secret_value(),
198
- url=self.url,
202
+ url=self.base_url,
199
203
  project_id=self.project_id,
200
204
  model_id=self.model_name,
201
205
  params=chat_params,
@@ -60,6 +60,13 @@ class ChatInput(ChatComponent):
60
60
  info="The session ID of the chat. If empty, the current session ID parameter will be used.",
61
61
  advanced=True,
62
62
  ),
63
+ MessageTextInput(
64
+ name="context_id",
65
+ display_name="Context ID",
66
+ info="The context ID of the chat. Adds an extra layer to the local memory.",
67
+ value="",
68
+ advanced=True,
69
+ ),
63
70
  FileInput(
64
71
  name="files",
65
72
  display_name="Files",
@@ -87,6 +94,7 @@ class ChatInput(ChatComponent):
87
94
  sender=self.sender,
88
95
  sender_name=self.sender_name,
89
96
  session_id=self.session_id,
97
+ context_id=self.context_id,
90
98
  files=files,
91
99
  )
92
100
  if self.session_id and isinstance(message, Message) and self.should_store_message:
@@ -63,6 +63,13 @@ class ChatOutput(ChatComponent):
63
63
  info="The session ID of the chat. If empty, the current session ID parameter will be used.",
64
64
  advanced=True,
65
65
  ),
66
+ MessageTextInput(
67
+ name="context_id",
68
+ display_name="Context ID",
69
+ info="The context ID of the chat. Adds an extra layer to the local memory.",
70
+ value="",
71
+ advanced=True,
72
+ ),
66
73
  MessageTextInput(
67
74
  name="data_template",
68
75
  display_name="Data Template",
@@ -121,6 +128,7 @@ class ChatOutput(ChatComponent):
121
128
  message.sender = self.sender
122
129
  message.sender_name = self.sender_name
123
130
  message.session_id = self.session_id
131
+ message.context_id = self.context_id
124
132
  message.flow_id = self.graph.flow_id if hasattr(self, "graph") else None
125
133
  message.properties.source = self._build_source(source_id, display_name, source)
126
134
 
@@ -48,12 +48,20 @@ HUGGINGFACE_MODEL_NAMES = [
48
48
  ]
49
49
  COHERE_MODEL_NAMES = ["embed-english-v3.0", "embed-multilingual-v3.0"]
50
50
 
51
- settings = get_settings_service().settings
52
- knowledge_directory = settings.knowledge_bases_dir
53
- if not knowledge_directory:
54
- msg = "Knowledge bases directory is not set in the settings."
55
- raise ValueError(msg)
56
- KNOWLEDGE_BASES_ROOT_PATH = Path(knowledge_directory).expanduser()
51
+ _KNOWLEDGE_BASES_ROOT_PATH: Path | None = None
52
+
53
+
54
+ def _get_knowledge_bases_root_path() -> Path:
55
+ """Lazy load the knowledge bases root path from settings."""
56
+ global _KNOWLEDGE_BASES_ROOT_PATH # noqa: PLW0603
57
+ if _KNOWLEDGE_BASES_ROOT_PATH is None:
58
+ settings = get_settings_service().settings
59
+ knowledge_directory = settings.knowledge_bases_dir
60
+ if not knowledge_directory:
61
+ msg = "Knowledge bases directory is not set in the settings."
62
+ raise ValueError(msg)
63
+ _KNOWLEDGE_BASES_ROOT_PATH = Path(knowledge_directory).expanduser()
64
+ return _KNOWLEDGE_BASES_ROOT_PATH
57
65
 
58
66
 
59
67
  class KnowledgeIngestionComponent(Component):
@@ -203,7 +211,7 @@ class KnowledgeIngestionComponent(Component):
203
211
  # ------ Internal helpers ---------------------------------------------
204
212
  def _get_kb_root(self) -> Path:
205
213
  """Return the root directory for knowledge bases."""
206
- return KNOWLEDGE_BASES_ROOT_PATH
214
+ return _get_knowledge_bases_root_path()
207
215
 
208
216
  def _validate_column_config(self, df_source: pd.DataFrame) -> list[dict[str, Any]]:
209
217
  """Validate column configuration using Structured Output patterns."""
@@ -662,7 +670,7 @@ class KnowledgeIngestionComponent(Component):
662
670
  raise ValueError(msg) from e
663
671
 
664
672
  # Create the new knowledge base directory
665
- kb_path = KNOWLEDGE_BASES_ROOT_PATH / kb_user / field_value["01_new_kb_name"]
673
+ kb_path = _get_knowledge_bases_root_path() / kb_user / field_value["01_new_kb_name"]
666
674
  kb_path.mkdir(parents=True, exist_ok=True)
667
675
 
668
676
  # Save the embedding metadata
@@ -675,7 +683,7 @@ class KnowledgeIngestionComponent(Component):
675
683
 
676
684
  # Update the knowledge base options dynamically
677
685
  build_config["knowledge_base"]["options"] = await get_knowledge_bases(
678
- KNOWLEDGE_BASES_ROOT_PATH,
686
+ _get_knowledge_bases_root_path(),
679
687
  user_id=self.user_id,
680
688
  )
681
689
 
@@ -16,12 +16,20 @@ from lfx.schema.data import Data
16
16
  from lfx.schema.dataframe import DataFrame
17
17
  from lfx.services.deps import get_settings_service, session_scope
18
18
 
19
- settings = get_settings_service().settings
20
- knowledge_directory = settings.knowledge_bases_dir
21
- if not knowledge_directory:
22
- msg = "Knowledge bases directory is not set in the settings."
23
- raise ValueError(msg)
24
- KNOWLEDGE_BASES_ROOT_PATH = Path(knowledge_directory).expanduser()
19
+ _KNOWLEDGE_BASES_ROOT_PATH: Path | None = None
20
+
21
+
22
+ def _get_knowledge_bases_root_path() -> Path:
23
+ """Lazy load the knowledge bases root path from settings."""
24
+ global _KNOWLEDGE_BASES_ROOT_PATH # noqa: PLW0603
25
+ if _KNOWLEDGE_BASES_ROOT_PATH is None:
26
+ settings = get_settings_service().settings
27
+ knowledge_directory = settings.knowledge_bases_dir
28
+ if not knowledge_directory:
29
+ msg = "Knowledge bases directory is not set in the settings."
30
+ raise ValueError(msg)
31
+ _KNOWLEDGE_BASES_ROOT_PATH = Path(knowledge_directory).expanduser()
32
+ return _KNOWLEDGE_BASES_ROOT_PATH
25
33
 
26
34
 
27
35
  class KnowledgeRetrievalComponent(Component):
@@ -90,7 +98,7 @@ class KnowledgeRetrievalComponent(Component):
90
98
  if field_name == "knowledge_base":
91
99
  # Update the knowledge base options dynamically
92
100
  build_config["knowledge_base"]["options"] = await get_knowledge_bases(
93
- KNOWLEDGE_BASES_ROOT_PATH,
101
+ _get_knowledge_bases_root_path(),
94
102
  user_id=self.user_id, # Use the user_id from the component context
95
103
  )
96
104
 
@@ -186,7 +194,7 @@ class KnowledgeRetrievalComponent(Component):
186
194
  msg = f"User with ID {self.user_id} not found."
187
195
  raise ValueError(msg)
188
196
  kb_user = current_user.username
189
- kb_path = KNOWLEDGE_BASES_ROOT_PATH / kb_user / self.knowledge_base
197
+ kb_path = _get_knowledge_bases_root_path() / kb_user / self.knowledge_base
190
198
 
191
199
  metadata = self._get_kb_metadata(kb_path)
192
200
  if not metadata:
@@ -89,6 +89,10 @@ class LoopComponent(Component):
89
89
  item_dependency_id = self.get_incoming_edge_by_target_param("item")
90
90
  if item_dependency_id not in self.graph.run_manager.run_predecessors[self._id]:
91
91
  self.graph.run_manager.run_predecessors[self._id].append(item_dependency_id)
92
+ # CRITICAL: Also update run_map so remove_from_predecessors() works correctly
93
+ # run_map[predecessor] = list of vertices that depend on predecessor
94
+ if self._id not in self.graph.run_manager.run_map[item_dependency_id]:
95
+ self.graph.run_manager.run_map[item_dependency_id].append(self._id)
92
96
 
93
97
  def done_output(self) -> DataFrame:
94
98
  """Trigger the done output when iteration is complete."""
@@ -1,4 +1,4 @@
1
- from langchain_mistralai.embeddings import MistralAIEmbeddings
1
+ from langchain_mistralai import MistralAIEmbeddings
2
2
  from pydantic.v1 import SecretStr
3
3
 
4
4
  from lfx.base.models.model import LCModelComponent