lfx-nightly 0.1.12.dev42__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 (93) 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/cometapi_constants.py +54 -0
  11. lfx/base/models/model_input_constants.py +74 -7
  12. lfx/base/models/ollama_constants.py +3 -0
  13. lfx/base/models/watsonx_constants.py +12 -0
  14. lfx/cli/commands.py +1 -1
  15. lfx/components/agents/__init__.py +3 -1
  16. lfx/components/agents/agent.py +47 -4
  17. lfx/components/agents/altk_agent.py +366 -0
  18. lfx/components/agents/cuga_agent.py +1 -1
  19. lfx/components/agents/mcp_component.py +32 -2
  20. lfx/components/amazon/amazon_bedrock_converse.py +1 -1
  21. lfx/components/apify/apify_actor.py +3 -3
  22. lfx/components/cometapi/__init__.py +32 -0
  23. lfx/components/cometapi/cometapi.py +166 -0
  24. lfx/components/datastax/__init__.py +12 -6
  25. lfx/components/datastax/{astra_assistant_manager.py → astradb_assistant_manager.py} +1 -0
  26. lfx/components/datastax/astradb_chatmemory.py +40 -0
  27. lfx/components/datastax/astradb_cql.py +5 -31
  28. lfx/components/datastax/astradb_graph.py +9 -123
  29. lfx/components/datastax/astradb_tool.py +12 -52
  30. lfx/components/datastax/astradb_vectorstore.py +133 -976
  31. lfx/components/datastax/create_assistant.py +1 -0
  32. lfx/components/datastax/create_thread.py +1 -0
  33. lfx/components/datastax/dotenv.py +1 -0
  34. lfx/components/datastax/get_assistant.py +1 -0
  35. lfx/components/datastax/getenvvar.py +1 -0
  36. lfx/components/datastax/graph_rag.py +1 -1
  37. lfx/components/datastax/list_assistants.py +1 -0
  38. lfx/components/datastax/run.py +1 -0
  39. lfx/components/docling/__init__.py +3 -0
  40. lfx/components/docling/docling_remote_vlm.py +284 -0
  41. lfx/components/helpers/memory.py +19 -4
  42. lfx/components/ibm/watsonx.py +25 -21
  43. lfx/components/input_output/chat.py +8 -0
  44. lfx/components/input_output/chat_output.py +8 -0
  45. lfx/components/knowledge_bases/ingestion.py +17 -9
  46. lfx/components/knowledge_bases/retrieval.py +16 -8
  47. lfx/components/logic/loop.py +4 -0
  48. lfx/components/mistral/mistral_embeddings.py +1 -1
  49. lfx/components/models/embedding_model.py +88 -7
  50. lfx/components/ollama/ollama.py +221 -14
  51. lfx/components/openrouter/openrouter.py +49 -147
  52. lfx/components/processing/parser.py +6 -1
  53. lfx/components/processing/structured_output.py +55 -17
  54. lfx/components/vectorstores/__init__.py +0 -6
  55. lfx/custom/custom_component/component.py +3 -2
  56. lfx/field_typing/constants.py +1 -0
  57. lfx/graph/edge/base.py +2 -2
  58. lfx/graph/graph/base.py +1 -1
  59. lfx/graph/graph/schema.py +3 -2
  60. lfx/graph/vertex/vertex_types.py +1 -1
  61. lfx/io/schema.py +6 -0
  62. lfx/memory/stubs.py +26 -7
  63. lfx/schema/message.py +6 -0
  64. lfx/schema/schema.py +5 -0
  65. lfx/services/settings/constants.py +1 -0
  66. {lfx_nightly-0.1.12.dev42.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/METADATA +1 -1
  67. {lfx_nightly-0.1.12.dev42.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/RECORD +70 -85
  68. lfx/components/datastax/astra_db.py +0 -77
  69. lfx/components/datastax/cassandra.py +0 -92
  70. lfx/components/vectorstores/astradb_graph.py +0 -326
  71. lfx/components/vectorstores/cassandra.py +0 -264
  72. lfx/components/vectorstores/cassandra_graph.py +0 -238
  73. lfx/components/vectorstores/chroma.py +0 -167
  74. lfx/components/vectorstores/clickhouse.py +0 -135
  75. lfx/components/vectorstores/couchbase.py +0 -102
  76. lfx/components/vectorstores/elasticsearch.py +0 -267
  77. lfx/components/vectorstores/faiss.py +0 -111
  78. lfx/components/vectorstores/graph_rag.py +0 -141
  79. lfx/components/vectorstores/hcd.py +0 -314
  80. lfx/components/vectorstores/milvus.py +0 -115
  81. lfx/components/vectorstores/mongodb_atlas.py +0 -213
  82. lfx/components/vectorstores/opensearch.py +0 -243
  83. lfx/components/vectorstores/pgvector.py +0 -72
  84. lfx/components/vectorstores/pinecone.py +0 -134
  85. lfx/components/vectorstores/qdrant.py +0 -109
  86. lfx/components/vectorstores/supabase.py +0 -76
  87. lfx/components/vectorstores/upstash.py +0 -124
  88. lfx/components/vectorstores/vectara.py +0 -97
  89. lfx/components/vectorstores/vectara_rag.py +0 -164
  90. lfx/components/vectorstores/weaviate.py +0 -89
  91. /lfx/components/datastax/{astra_vectorize.py → astradb_vectorize.py} +0 -0
  92. {lfx_nightly-0.1.12.dev42.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/WHEEL +0 -0
  93. {lfx_nightly-0.1.12.dev42.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,3 @@
1
- from collections import defaultdict
2
- from typing import Any
3
-
4
1
  import httpx
5
2
  from langchain_openai import ChatOpenAI
6
3
  from pydantic.v1 import SecretStr
@@ -8,13 +5,7 @@ from pydantic.v1 import SecretStr
8
5
  from lfx.base.models.model import LCModelComponent
9
6
  from lfx.field_typing import LanguageModel
10
7
  from lfx.field_typing.range_spec import RangeSpec
11
- from lfx.inputs.inputs import (
12
- DropdownInput,
13
- IntInput,
14
- SecretStrInput,
15
- SliderInput,
16
- StrInput,
17
- )
8
+ from lfx.inputs.inputs import DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput
18
9
 
19
10
 
20
11
  class OpenRouterComponent(LCModelComponent):
@@ -28,36 +19,13 @@ class OpenRouterComponent(LCModelComponent):
28
19
 
29
20
  inputs = [
30
21
  *LCModelComponent.get_base_inputs(),
31
- SecretStrInput(
32
- name="api_key", display_name="OpenRouter API Key", required=True, info="Your OpenRouter API key"
33
- ),
34
- StrInput(
35
- name="site_url",
36
- display_name="Site URL",
37
- info="Your site URL for OpenRouter rankings",
38
- advanced=True,
39
- ),
40
- StrInput(
41
- name="app_name",
42
- display_name="App Name",
43
- info="Your app name for OpenRouter rankings",
44
- advanced=True,
45
- ),
46
- DropdownInput(
47
- name="provider",
48
- display_name="Provider",
49
- info="The AI model provider",
50
- options=["Loading providers..."],
51
- value="Loading providers...",
52
- real_time_refresh=True,
53
- required=True,
54
- ),
22
+ SecretStrInput(name="api_key", display_name="API Key", required=True),
55
23
  DropdownInput(
56
24
  name="model_name",
57
25
  display_name="Model",
58
- info="The model to use for chat completion",
59
- options=["Select a provider first"],
60
- value="Select a provider first",
26
+ options=[],
27
+ value="",
28
+ refresh_button=True,
61
29
  real_time_refresh=True,
62
30
  required=True,
63
31
  ),
@@ -66,137 +34,71 @@ class OpenRouterComponent(LCModelComponent):
66
34
  display_name="Temperature",
67
35
  value=0.7,
68
36
  range_spec=RangeSpec(min=0, max=2, step=0.01),
69
- info="Controls randomness. Lower values are more deterministic, higher values are more creative.",
70
- advanced=True,
71
- ),
72
- IntInput(
73
- name="max_tokens",
74
- display_name="Max Tokens",
75
- info="Maximum number of tokens to generate",
76
37
  advanced=True,
77
38
  ),
39
+ IntInput(name="max_tokens", display_name="Max Tokens", advanced=True),
40
+ StrInput(name="site_url", display_name="Site URL", advanced=True),
41
+ StrInput(name="app_name", display_name="App Name", advanced=True),
78
42
  ]
79
43
 
80
- def fetch_models(self) -> dict[str, list]:
81
- """Fetch available models from OpenRouter API and organize them by provider."""
82
- url = "https://openrouter.ai/api/v1/models"
83
-
44
+ def fetch_models(self) -> list[dict]:
45
+ """Fetch available models from OpenRouter."""
84
46
  try:
85
- with httpx.Client() as client:
86
- response = client.get(url)
87
- response.raise_for_status()
88
-
89
- models_data = response.json().get("data", [])
90
- provider_models = defaultdict(list)
91
-
92
- for model in models_data:
93
- model_id = model.get("id", "")
94
- if "/" in model_id:
95
- provider = model_id.split("/")[0].title()
96
- provider_models[provider].append(
97
- {
98
- "id": model_id,
99
- "name": model.get("name", ""),
100
- "description": model.get("description", ""),
101
- "context_length": model.get("context_length", 0),
102
- }
103
- )
104
-
105
- return dict(provider_models)
106
-
107
- except httpx.HTTPError as e:
108
- self.log(f"Error fetching models: {e!s}")
109
- return {"Error": [{"id": "error", "name": f"Error fetching models: {e!s}"}]}
47
+ response = httpx.get("https://openrouter.ai/api/v1/models", timeout=10.0)
48
+ response.raise_for_status()
49
+ models = response.json().get("data", [])
50
+ return sorted(
51
+ [
52
+ {
53
+ "id": m["id"],
54
+ "name": m.get("name", m["id"]),
55
+ "context": m.get("context_length", 0),
56
+ }
57
+ for m in models
58
+ if m.get("id")
59
+ ],
60
+ key=lambda x: x["name"],
61
+ )
62
+ except (httpx.RequestError, httpx.HTTPStatusError) as e:
63
+ self.log(f"Error fetching models: {e}")
64
+ return []
65
+
66
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict: # noqa: ARG002
67
+ """Update model options."""
68
+ models = self.fetch_models()
69
+ if models:
70
+ build_config["model_name"]["options"] = [m["id"] for m in models]
71
+ build_config["model_name"]["tooltips"] = {m["id"]: f"{m['name']} ({m['context']:,} tokens)" for m in models}
72
+ else:
73
+ build_config["model_name"]["options"] = ["Failed to load models"]
74
+ build_config["model_name"]["value"] = "Failed to load models"
75
+ return build_config
110
76
 
111
77
  def build_model(self) -> LanguageModel:
112
- """Build and return the OpenRouter language model."""
113
- model_not_selected = "Please select a model"
114
- api_key_required = "API key is required"
115
-
116
- if not self.model_name or self.model_name == "Select a provider first":
117
- raise ValueError(model_not_selected)
118
-
78
+ """Build the OpenRouter model."""
119
79
  if not self.api_key:
120
- raise ValueError(api_key_required)
121
-
122
- api_key = SecretStr(self.api_key).get_secret_value()
80
+ msg = "API key is required"
81
+ raise ValueError(msg)
82
+ if not self.model_name or self.model_name == "Loading...":
83
+ msg = "Please select a model"
84
+ raise ValueError(msg)
123
85
 
124
- # Build base configuration
125
- kwargs: dict[str, Any] = {
86
+ kwargs = {
126
87
  "model": self.model_name,
127
- "openai_api_key": api_key,
88
+ "openai_api_key": SecretStr(self.api_key).get_secret_value(),
128
89
  "openai_api_base": "https://openrouter.ai/api/v1",
129
90
  "temperature": self.temperature if self.temperature is not None else 0.7,
130
91
  }
131
92
 
132
- # Add optional parameters
133
93
  if self.max_tokens:
134
- kwargs["max_tokens"] = self.max_tokens
94
+ kwargs["max_tokens"] = int(self.max_tokens)
135
95
 
136
96
  headers = {}
137
97
  if self.site_url:
138
98
  headers["HTTP-Referer"] = self.site_url
139
99
  if self.app_name:
140
100
  headers["X-Title"] = self.app_name
141
-
142
101
  if headers:
143
102
  kwargs["default_headers"] = headers
144
103
 
145
- try:
146
- return ChatOpenAI(**kwargs)
147
- except (ValueError, httpx.HTTPError) as err:
148
- error_msg = f"Failed to build model: {err!s}"
149
- self.log(error_msg)
150
- raise ValueError(error_msg) from err
151
-
152
- def _get_exception_message(self, e: Exception) -> str | None:
153
- """Get a message from an OpenRouter exception.
154
-
155
- Args:
156
- e (Exception): The exception to get the message from.
157
-
158
- Returns:
159
- str | None: The message from the exception, or None if no specific message can be extracted.
160
- """
161
- try:
162
- from openai import BadRequestError
163
-
164
- if isinstance(e, BadRequestError):
165
- message = e.body.get("message")
166
- if message:
167
- return message
168
- except ImportError:
169
- pass
170
- return None
171
-
172
- def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:
173
- """Update build configuration based on field updates."""
174
- try:
175
- if field_name is None or field_name == "provider":
176
- provider_models = self.fetch_models()
177
- build_config["provider"]["options"] = sorted(provider_models.keys())
178
- if build_config["provider"]["value"] not in provider_models:
179
- build_config["provider"]["value"] = build_config["provider"]["options"][0]
180
-
181
- if field_name == "provider" and field_value in self.fetch_models():
182
- provider_models = self.fetch_models()
183
- models = provider_models[field_value]
184
-
185
- build_config["model_name"]["options"] = [model["id"] for model in models]
186
- if models:
187
- build_config["model_name"]["value"] = models[0]["id"]
188
-
189
- tooltips = {
190
- model["id"]: (f"{model['name']}\nContext Length: {model['context_length']}\n{model['description']}")
191
- for model in models
192
- }
193
- build_config["model_name"]["tooltips"] = tooltips
194
-
195
- except httpx.HTTPError as e:
196
- self.log(f"Error updating build config: {e!s}")
197
- build_config["provider"]["options"] = ["Error loading providers"]
198
- build_config["provider"]["value"] = "Error loading providers"
199
- build_config["model_name"]["options"] = ["Error loading models"]
200
- build_config["model_name"]["value"] = "Error loading models"
201
-
202
- return build_config
104
+ return ChatOpenAI(**kwargs)
@@ -122,7 +122,12 @@ class ParserComponent(Component):
122
122
  formatted_text = self.pattern.format(**row.to_dict())
123
123
  lines.append(formatted_text)
124
124
  elif data is not None:
125
- formatted_text = self.pattern.format(**data.data)
125
+ # Use format_map with a dict that returns default_value for missing keys
126
+ class DefaultDict(dict):
127
+ def __missing__(self, key):
128
+ return data.default_value or ""
129
+
130
+ formatted_text = self.pattern.format_map(DefaultDict(data.data))
126
131
  lines.append(formatted_text)
127
132
 
128
133
  combined_text = self.sep.join(lines)
@@ -11,6 +11,7 @@ from lfx.io import (
11
11
  Output,
12
12
  TableInput,
13
13
  )
14
+ from lfx.log.logger import logger
14
15
  from lfx.schema.data import Data
15
16
  from lfx.schema.dataframe import DataFrame
16
17
  from lfx.schema.table import EditMode
@@ -136,30 +137,27 @@ class StructuredOutputComponent(Component):
136
137
  raise ValueError(msg)
137
138
 
138
139
  output_model_ = build_model_from_schema(self.output_schema)
139
-
140
140
  output_model = create_model(
141
141
  schema_name,
142
142
  __doc__=f"A list of {schema_name}.",
143
- objects=(list[output_model_], Field(description=f"A list of {schema_name}.")), # type: ignore[valid-type]
143
+ objects=(
144
+ list[output_model_],
145
+ Field(
146
+ description=f"A list of {schema_name}.", # type: ignore[valid-type]
147
+ min_length=1, # help ensure non-empty output
148
+ ),
149
+ ),
144
150
  )
145
-
146
- try:
147
- llm_with_structured_output = create_extractor(self.llm, tools=[output_model])
148
- except NotImplementedError as exc:
149
- msg = f"{self.llm.__class__.__name__} does not support structured output."
150
- raise TypeError(msg) from exc
151
-
151
+ # Tracing config
152
152
  config_dict = {
153
153
  "run_name": self.display_name,
154
154
  "project_name": self.get_project_name(),
155
155
  "callbacks": self.get_langchain_callbacks(),
156
156
  }
157
- result = get_chat_result(
158
- runnable=llm_with_structured_output,
159
- system_message=self.system_prompt,
160
- input_value=self.input_value,
161
- config=config_dict,
162
- )
157
+ # Generate structured output using Trustcall first, then fallback to Langchain if it fails
158
+ result = self._extract_output_with_trustcall(output_model, config_dict)
159
+ if result is None:
160
+ result = self._extract_output_with_langchain(output_model, config_dict)
163
161
 
164
162
  # OPTIMIZATION NOTE: Simplified processing based on trustcall response structure
165
163
  # Handle non-dict responses (shouldn't happen with trustcall, but defensive)
@@ -173,8 +171,9 @@ class StructuredOutputComponent(Component):
173
171
 
174
172
  # Convert BaseModel to dict (creates the "objects" key)
175
173
  first_response = responses[0]
176
- structured_data = first_response.model_dump() if isinstance(first_response, BaseModel) else first_response
177
-
174
+ structured_data = first_response
175
+ if isinstance(first_response, BaseModel):
176
+ structured_data = first_response.model_dump()
178
177
  # Extract the objects array (guaranteed to exist due to our Pydantic model structure)
179
178
  return structured_data.get("objects", structured_data)
180
179
 
@@ -204,3 +203,42 @@ class StructuredOutputComponent(Component):
204
203
  # Multiple outputs - convert to DataFrame directly
205
204
  return DataFrame(output)
206
205
  return DataFrame()
206
+
207
+ def _extract_output_with_trustcall(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:
208
+ try:
209
+ llm_with_structured_output = create_extractor(self.llm, tools=[schema], tool_choice=schema.__name__)
210
+ result = get_chat_result(
211
+ runnable=llm_with_structured_output,
212
+ system_message=self.system_prompt,
213
+ input_value=self.input_value,
214
+ config=config_dict,
215
+ )
216
+ except Exception as e: # noqa: BLE001
217
+ logger.warning(
218
+ f"Trustcall extraction failed, falling back to Langchain: {e} "
219
+ "(Note: This may not be an error—some models or configurations do not support tool calling. "
220
+ "Falling back is normal in such cases.)"
221
+ )
222
+ return None
223
+ return result or None # langchain fallback is used if error occurs or the result is empty
224
+
225
+ def _extract_output_with_langchain(self, schema: BaseModel, config_dict: dict) -> list[BaseModel] | None:
226
+ try:
227
+ llm_with_structured_output = self.llm.with_structured_output(schema)
228
+ result = get_chat_result(
229
+ runnable=llm_with_structured_output,
230
+ system_message=self.system_prompt,
231
+ input_value=self.input_value,
232
+ config=config_dict,
233
+ )
234
+ if isinstance(result, BaseModel):
235
+ result = result.model_dump()
236
+ result = result.get("objects", result)
237
+ except Exception as fallback_error:
238
+ msg = (
239
+ f"Model does not support tool calling (trustcall failed) "
240
+ f"and fallback with_structured_output also failed: {fallback_error}"
241
+ )
242
+ raise ValueError(msg) from fallback_error
243
+
244
+ return result or None
@@ -5,20 +5,14 @@ from typing import TYPE_CHECKING, Any
5
5
  from lfx.components._importing import import_mod
6
6
 
7
7
  if TYPE_CHECKING:
8
- from .astradb import AstraDBVectorStoreComponent
9
8
  from .local_db import LocalDBComponent
10
- from .mongodb_atlas import MongoVectorStoreComponent
11
9
 
12
10
  _dynamic_imports = {
13
11
  "LocalDBComponent": "local_db",
14
- "AstraDBVectorStoreComponent": "astradb",
15
- "MongoVectorStoreComponent": "mongodb_atlas",
16
12
  }
17
13
 
18
14
  __all__ = [
19
- "AstraDBVectorStoreComponent",
20
15
  "LocalDBComponent",
21
- "MongoVectorStoreComponent",
22
16
  ]
23
17
 
24
18
 
@@ -154,7 +154,7 @@ class Component(CustomComponent):
154
154
  self.trace_type = "chain"
155
155
 
156
156
  # Setup inputs and outputs
157
- self._reset_all_output_values()
157
+ self.reset_all_output_values()
158
158
  if self.inputs is not None:
159
159
  self.map_inputs(self.inputs)
160
160
  self.map_outputs()
@@ -330,7 +330,8 @@ class Component(CustomComponent):
330
330
  def set_event_manager(self, event_manager: EventManager | None = None) -> None:
331
331
  self._event_manager = event_manager
332
332
 
333
- def _reset_all_output_values(self) -> None:
333
+ def reset_all_output_values(self) -> None:
334
+ """Reset all output values to UNDEFINED."""
334
335
  if isinstance(self._outputs_map, dict):
335
336
  for output in self._outputs_map.values():
336
337
  output.value = UNDEFINED
@@ -209,6 +209,7 @@ from lfx.io import (
209
209
  TableInput,
210
210
  )
211
211
  from lfx.schema.data import Data
212
+ from lfx.schema.dataframe import DataFrame
212
213
  """
213
214
 
214
215
  if importlib.util.find_spec("langchain") is not None:
lfx/graph/edge/base.py CHANGED
@@ -63,8 +63,8 @@ class Edge:
63
63
  # target_param is documents
64
64
  if isinstance(self._target_handle, str):
65
65
  self.target_param = self._target_handle.split("|")[1]
66
- self.source_handle = None
67
- self.target_handle = None
66
+ self.source_handle = None # type: ignore[assignment]
67
+ self.target_handle = None # type: ignore[assignment]
68
68
  else:
69
69
  msg = "Target handle is not a string"
70
70
  raise ValueError(msg)
lfx/graph/graph/base.py CHANGED
@@ -402,7 +402,7 @@ class Graph:
402
402
  for vertex in self.vertices:
403
403
  if vertex.custom_component is None:
404
404
  continue
405
- vertex.custom_component._reset_all_output_values()
405
+ vertex.custom_component.reset_all_output_values()
406
406
 
407
407
  def start(
408
408
  self,
lfx/graph/graph/schema.py CHANGED
@@ -4,11 +4,12 @@ from typing import TYPE_CHECKING, NamedTuple, Protocol
4
4
 
5
5
  from typing_extensions import NotRequired, TypedDict
6
6
 
7
+ from lfx.graph.edge.schema import EdgeData
8
+ from lfx.graph.vertex.schema import NodeData
9
+
7
10
  if TYPE_CHECKING:
8
- from lfx.graph.edge.schema import EdgeData
9
11
  from lfx.graph.schema import ResultData
10
12
  from lfx.graph.vertex.base import Vertex
11
- from lfx.graph.vertex.schema import NodeData
12
13
  from lfx.schema.log import LoggableType
13
14
 
14
15
 
@@ -65,7 +65,7 @@ class ComponentVertex(Vertex):
65
65
  self.built_object, self.artifacts = result
66
66
  elif len(result) == 3: # noqa: PLR2004
67
67
  self.custom_component, self.built_object, self.artifacts = result
68
- self.logs = self.custom_component._output_logs
68
+ self.logs = self.custom_component.get_output_logs()
69
69
  for key in self.artifacts:
70
70
  if self.artifacts_raw is None:
71
71
  self.artifacts_raw = {}
lfx/io/schema.py CHANGED
@@ -137,6 +137,12 @@ def schema_to_langflow_inputs(schema: type[BaseModel]) -> list[InputTypes]:
137
137
 
138
138
  is_list = False
139
139
 
140
+ # Handle unparameterized list (e.g., coming from nullable array schemas)
141
+ # Treat it as a list of strings for input purposes
142
+ if ann is list:
143
+ is_list = True
144
+ ann = str
145
+
140
146
  if get_origin(ann) is list:
141
147
  is_list = True
142
148
  ann = get_args(ann)[0]
lfx/memory/stubs.py CHANGED
@@ -165,6 +165,7 @@ async def aget_messages(
165
165
  sender: str | None = None, # noqa: ARG001
166
166
  sender_name: str | None = None, # noqa: ARG001
167
167
  session_id: str | UUID | None = None, # noqa: ARG001
168
+ context_id: str | UUID | None = None, # noqa: ARG001
168
169
  order_by: str | None = "timestamp", # noqa: ARG001
169
170
  order: str | None = "DESC", # noqa: ARG001
170
171
  flow_id: UUID | None = None, # noqa: ARG001
@@ -176,6 +177,7 @@ async def aget_messages(
176
177
  sender (Optional[str]): The sender of the messages (e.g., "Machine" or "User")
177
178
  sender_name (Optional[str]): The name of the sender.
178
179
  session_id (Optional[str]): The session ID associated with the messages.
180
+ context_id (Optional[str]): The context ID associated with the messages.
179
181
  order_by (Optional[str]): The field to order the messages by. Defaults to "timestamp".
180
182
  order (Optional[str]): The order in which to retrieve the messages. Defaults to "DESC".
181
183
  flow_id (Optional[UUID]): The flow ID associated with the messages.
@@ -200,6 +202,7 @@ def get_messages(
200
202
  sender: str | None = None,
201
203
  sender_name: str | None = None,
202
204
  session_id: str | UUID | None = None,
205
+ context_id: str | UUID | None = None,
203
206
  order_by: str | None = "timestamp",
204
207
  order: str | None = "DESC",
205
208
  flow_id: UUID | None = None,
@@ -209,33 +212,49 @@ def get_messages(
209
212
 
210
213
  DEPRECATED: Use `aget_messages` instead.
211
214
  """
212
- return run_until_complete(aget_messages(sender, sender_name, session_id, order_by, order, flow_id, limit))
215
+ return run_until_complete(
216
+ aget_messages(
217
+ sender,
218
+ sender_name,
219
+ session_id,
220
+ context_id,
221
+ order_by,
222
+ order,
223
+ flow_id,
224
+ limit,
225
+ )
226
+ )
213
227
 
214
228
 
215
- async def adelete_messages(session_id: str) -> None:
216
- """Delete messages from the memory based on the provided session ID.
229
+ async def adelete_messages(session_id: str | None = None, context_id: str | None = None) -> None:
230
+ """Delete messages from the memory based on the provided session or context ID.
217
231
 
218
232
  Args:
219
233
  session_id (str): The session ID associated with the messages to delete.
234
+ context_id (str): The context ID associated with the messages to delete.
220
235
  """
236
+ if not session_id and not context_id:
237
+ msg = "Either session_id or context_id must be provided to delete messages."
238
+ raise ValueError(msg)
239
+
221
240
  async with session_scope() as session:
222
241
  try:
223
242
  # In a real implementation, this would delete from database
224
243
  # For now, this is a no-op since we're using NoopSession
225
- await session.delete(session_id)
244
+ await session.delete(session_id or context_id) # type: ignore # noqa: PGH003
226
245
  await session.commit()
227
- logger.debug(f"Messages deleted for session: {session_id}")
246
+ logger.debug(f"Messages deleted for session: {session_id or context_id}")
228
247
  except Exception as e:
229
248
  logger.exception(f"Error deleting messages: {e}")
230
249
  raise
231
250
 
232
251
 
233
- def delete_messages(session_id: str) -> None:
252
+ def delete_messages(session_id: str | None = None, context_id: str | None = None) -> None:
234
253
  """DEPRECATED - Delete messages based on the provided session ID.
235
254
 
236
255
  DEPRECATED: Use `adelete_messages` instead.
237
256
  """
238
- return run_until_complete(adelete_messages(session_id))
257
+ return run_until_complete(adelete_messages(session_id, context_id))
239
258
 
240
259
 
241
260
  async def aadd_messages(messages: Message | list[Message]) -> list[Message]:
lfx/schema/message.py CHANGED
@@ -40,6 +40,7 @@ class Message(Data):
40
40
  sender_name: str | None = None
41
41
  files: list[str | Image] | None = Field(default=[])
42
42
  session_id: str | UUID | None = Field(default="")
43
+ context_id: str | UUID | None = Field(default="")
43
44
  timestamp: Annotated[str, timestamp_to_str_validator] = Field(
44
45
  default_factory=lambda: datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z")
45
46
  )
@@ -188,6 +189,7 @@ class Message(Data):
188
189
  sender_name=data.sender_name,
189
190
  files=data.files,
190
191
  session_id=data.session_id,
192
+ context_id=data.context_id,
191
193
  timestamp=data.timestamp,
192
194
  flow_id=data.flow_id,
193
195
  error=data.error,
@@ -326,6 +328,7 @@ class MessageResponse(DefaultModel):
326
328
  sender: str
327
329
  sender_name: str
328
330
  session_id: str
331
+ context_id: str | None = None
329
332
  text: str
330
333
  files: list[str] = []
331
334
  edit: bool
@@ -383,6 +386,7 @@ class MessageResponse(DefaultModel):
383
386
  sender_name=message.sender_name,
384
387
  text=message.text,
385
388
  session_id=message.session_id,
389
+ context_id=message.context_id,
386
390
  files=message.files or [],
387
391
  timestamp=message.timestamp,
388
392
  flow_id=flow_id,
@@ -433,6 +437,7 @@ class ErrorMessage(Message):
433
437
  self,
434
438
  exception: BaseException,
435
439
  session_id: str | None = None,
440
+ context_id: str | None = None,
436
441
  source: Source | None = None,
437
442
  trace_name: str | None = None,
438
443
  flow_id: UUID | str | None = None,
@@ -451,6 +456,7 @@ class ErrorMessage(Message):
451
456
 
452
457
  super().__init__(
453
458
  session_id=session_id,
459
+ context_id=context_id,
454
460
  sender=source.display_name if source else None,
455
461
  sender_name=source.display_name if source else None,
456
462
  text=plain_reason,
lfx/schema/schema.py CHANGED
@@ -149,6 +149,11 @@ class InputValueRequest(BaseModel):
149
149
  description="Defines on which components the input value should be applied. "
150
150
  "'any' applies to all input components.",
151
151
  )
152
+ client_request_time: int | None = Field(
153
+ None,
154
+ description="Client-side timestamp in milliseconds when the request was initiated. "
155
+ "Used to calculate accurate end-to-end duration.",
156
+ )
152
157
 
153
158
  # add an example
154
159
  model_config = ConfigDict(
@@ -31,4 +31,5 @@ VARIABLES_TO_GET_FROM_ENVIRONMENT = [
31
31
  "AWS_SECRET_ACCESS_KEY",
32
32
  "NOVITA_API_KEY",
33
33
  "TAVILY_API_KEY",
34
+ "COMETAPI_KEY",
34
35
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lfx-nightly
3
- Version: 0.1.12.dev42
3
+ Version: 0.2.0.dev0
4
4
  Summary: Langflow Executor - A lightweight CLI tool for executing and serving Langflow AI flows
5
5
  Author-email: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
6
6
  Requires-Python: <3.14,>=3.10