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.
- lfx/_assets/component_index.json +1 -1
- lfx/base/agents/agent.py +109 -29
- lfx/base/agents/events.py +102 -35
- lfx/base/agents/utils.py +15 -2
- lfx/base/composio/composio_base.py +24 -9
- lfx/base/datastax/__init__.py +5 -0
- lfx/{components/vectorstores/astradb.py → base/datastax/astradb_base.py} +84 -473
- lfx/base/io/chat.py +5 -4
- lfx/base/mcp/util.py +101 -15
- lfx/base/models/cometapi_constants.py +54 -0
- lfx/base/models/model_input_constants.py +74 -7
- lfx/base/models/ollama_constants.py +3 -0
- lfx/base/models/watsonx_constants.py +12 -0
- lfx/cli/commands.py +1 -1
- lfx/components/agents/__init__.py +3 -1
- lfx/components/agents/agent.py +47 -4
- lfx/components/agents/altk_agent.py +366 -0
- lfx/components/agents/cuga_agent.py +1 -1
- lfx/components/agents/mcp_component.py +32 -2
- lfx/components/amazon/amazon_bedrock_converse.py +1 -1
- lfx/components/apify/apify_actor.py +3 -3
- lfx/components/cometapi/__init__.py +32 -0
- lfx/components/cometapi/cometapi.py +166 -0
- lfx/components/datastax/__init__.py +12 -6
- lfx/components/datastax/{astra_assistant_manager.py → astradb_assistant_manager.py} +1 -0
- lfx/components/datastax/astradb_chatmemory.py +40 -0
- lfx/components/datastax/astradb_cql.py +5 -31
- lfx/components/datastax/astradb_graph.py +9 -123
- lfx/components/datastax/astradb_tool.py +12 -52
- lfx/components/datastax/astradb_vectorstore.py +133 -976
- lfx/components/datastax/create_assistant.py +1 -0
- lfx/components/datastax/create_thread.py +1 -0
- lfx/components/datastax/dotenv.py +1 -0
- lfx/components/datastax/get_assistant.py +1 -0
- lfx/components/datastax/getenvvar.py +1 -0
- lfx/components/datastax/graph_rag.py +1 -1
- lfx/components/datastax/list_assistants.py +1 -0
- lfx/components/datastax/run.py +1 -0
- lfx/components/docling/__init__.py +3 -0
- lfx/components/docling/docling_remote_vlm.py +284 -0
- lfx/components/helpers/memory.py +19 -4
- lfx/components/ibm/watsonx.py +25 -21
- lfx/components/input_output/chat.py +8 -0
- lfx/components/input_output/chat_output.py +8 -0
- lfx/components/knowledge_bases/ingestion.py +17 -9
- lfx/components/knowledge_bases/retrieval.py +16 -8
- lfx/components/logic/loop.py +4 -0
- lfx/components/mistral/mistral_embeddings.py +1 -1
- lfx/components/models/embedding_model.py +88 -7
- lfx/components/ollama/ollama.py +221 -14
- lfx/components/openrouter/openrouter.py +49 -147
- lfx/components/processing/parser.py +6 -1
- lfx/components/processing/structured_output.py +55 -17
- lfx/components/vectorstores/__init__.py +0 -6
- lfx/custom/custom_component/component.py +3 -2
- lfx/field_typing/constants.py +1 -0
- lfx/graph/edge/base.py +2 -2
- lfx/graph/graph/base.py +1 -1
- lfx/graph/graph/schema.py +3 -2
- lfx/graph/vertex/vertex_types.py +1 -1
- lfx/io/schema.py +6 -0
- lfx/memory/stubs.py +26 -7
- lfx/schema/message.py +6 -0
- lfx/schema/schema.py +5 -0
- lfx/services/settings/constants.py +1 -0
- {lfx_nightly-0.1.12.dev42.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/METADATA +1 -1
- {lfx_nightly-0.1.12.dev42.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/RECORD +70 -85
- lfx/components/datastax/astra_db.py +0 -77
- lfx/components/datastax/cassandra.py +0 -92
- lfx/components/vectorstores/astradb_graph.py +0 -326
- lfx/components/vectorstores/cassandra.py +0 -264
- lfx/components/vectorstores/cassandra_graph.py +0 -238
- lfx/components/vectorstores/chroma.py +0 -167
- lfx/components/vectorstores/clickhouse.py +0 -135
- lfx/components/vectorstores/couchbase.py +0 -102
- lfx/components/vectorstores/elasticsearch.py +0 -267
- lfx/components/vectorstores/faiss.py +0 -111
- lfx/components/vectorstores/graph_rag.py +0 -141
- lfx/components/vectorstores/hcd.py +0 -314
- lfx/components/vectorstores/milvus.py +0 -115
- lfx/components/vectorstores/mongodb_atlas.py +0 -213
- lfx/components/vectorstores/opensearch.py +0 -243
- lfx/components/vectorstores/pgvector.py +0 -72
- lfx/components/vectorstores/pinecone.py +0 -134
- lfx/components/vectorstores/qdrant.py +0 -109
- lfx/components/vectorstores/supabase.py +0 -76
- lfx/components/vectorstores/upstash.py +0 -124
- lfx/components/vectorstores/vectara.py +0 -97
- lfx/components/vectorstores/vectara_rag.py +0 -164
- lfx/components/vectorstores/weaviate.py +0 -89
- /lfx/components/datastax/{astra_vectorize.py → astradb_vectorize.py} +0 -0
- {lfx_nightly-0.1.12.dev42.dist-info → lfx_nightly-0.2.0.dev0.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
81
|
-
"""Fetch available models from OpenRouter
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=(
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
lfx/field_typing/constants.py
CHANGED
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
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
|
|
lfx/graph/vertex/vertex_types.py
CHANGED
|
@@ -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.
|
|
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(
|
|
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(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lfx-nightly
|
|
3
|
-
Version: 0.
|
|
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
|