lfx-openai 0.1.0__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_openai/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """lfx-openai: OpenAI bundle.
2
+
3
+ Distribution unit ``lfx-openai``. At runtime Langflow's loader
4
+ discovers ``extension.json`` shipped alongside this ``__init__.py`` and
5
+ registers the bundle's components under the namespaced IDs
6
+ ``ext:openai:<Class>@official``.
7
+ """
8
+
9
+ from lfx_openai.components.openai.openai import OpenAIEmbeddingsComponent
10
+ from lfx_openai.components.openai.openai_chat_model import OpenAIModelComponent
11
+
12
+ __all__ = [
13
+ "OpenAIEmbeddingsComponent",
14
+ "OpenAIModelComponent",
15
+ ]
File without changes
@@ -0,0 +1,45 @@
1
+ """Lazy component re-exports for the ``openai`` bundle.
2
+
3
+ Mirrors the pre-extraction layout of ``lfx.components.openai`` so saved
4
+ flows that referenced the module-level class
5
+ (e.g. ``lfx.components.openai.<Class>``) keep resolving via the
6
+ migration table after rewrite to
7
+ ``lfx_openai.components.openai.<Class>``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from lfx.utils.lazy_import import import_mod
15
+
16
+ if TYPE_CHECKING:
17
+ from .openai import OpenAIEmbeddingsComponent
18
+ from .openai_chat_model import OpenAIModelComponent
19
+
20
+ _dynamic_imports = {
21
+ "OpenAIEmbeddingsComponent": "openai",
22
+ "OpenAIModelComponent": "openai_chat_model",
23
+ }
24
+
25
+ __all__ = [
26
+ "OpenAIEmbeddingsComponent",
27
+ "OpenAIModelComponent",
28
+ ]
29
+
30
+
31
+ def __getattr__(attr_name: str) -> Any:
32
+ if attr_name not in _dynamic_imports:
33
+ msg = f"module {__name__!r} has no attribute {attr_name!r}"
34
+ raise AttributeError(msg)
35
+ try:
36
+ result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent)
37
+ except (ModuleNotFoundError, ImportError, AttributeError) as e:
38
+ msg = f"Could not import {attr_name!r} from {__name__!r}: {e}"
39
+ raise AttributeError(msg) from e
40
+ globals()[attr_name] = result
41
+ return result
42
+
43
+
44
+ def __dir__() -> list[str]:
45
+ return list(__all__)
@@ -0,0 +1,99 @@
1
+ from langchain_openai import OpenAIEmbeddings
2
+ from lfx.base.embeddings.model import LCEmbeddingsModel
3
+ from lfx.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES
4
+ from lfx.field_typing import Embeddings
5
+ from lfx.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput
6
+
7
+
8
+ class OpenAIEmbeddingsComponent(LCEmbeddingsModel):
9
+ display_name = "OpenAI Embeddings"
10
+ description = "Generate embeddings using OpenAI models."
11
+ icon = "OpenAI"
12
+ name = "OpenAIEmbeddings"
13
+
14
+ inputs = [
15
+ DictInput(
16
+ name="default_headers",
17
+ display_name="Default Headers",
18
+ advanced=True,
19
+ info="Default headers to use for the API request.",
20
+ ),
21
+ DictInput(
22
+ name="default_query",
23
+ display_name="Default Query",
24
+ advanced=True,
25
+ info="Default query parameters to use for the API request.",
26
+ ),
27
+ IntInput(name="chunk_size", display_name="Chunk Size", advanced=True, value=1000),
28
+ MessageTextInput(name="client", display_name="Client", advanced=True),
29
+ MessageTextInput(name="deployment", display_name="Deployment", advanced=True),
30
+ IntInput(name="embedding_ctx_length", display_name="Embedding Context Length", advanced=True, value=1536),
31
+ IntInput(name="max_retries", display_name="Max Retries", value=3, advanced=True),
32
+ DropdownInput(
33
+ name="model",
34
+ display_name="Model",
35
+ advanced=False,
36
+ options=OPENAI_EMBEDDING_MODEL_NAMES,
37
+ value="text-embedding-3-small",
38
+ ),
39
+ DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
40
+ SecretStrInput(name="openai_api_key", display_name="OpenAI API Key", value="OPENAI_API_KEY", required=True),
41
+ MessageTextInput(name="openai_api_base", display_name="OpenAI API Base", advanced=True),
42
+ MessageTextInput(name="openai_api_type", display_name="OpenAI API Type", advanced=True),
43
+ MessageTextInput(name="openai_api_version", display_name="OpenAI API Version", advanced=True),
44
+ MessageTextInput(
45
+ name="openai_organization",
46
+ display_name="OpenAI Organization",
47
+ advanced=True,
48
+ ),
49
+ MessageTextInput(name="openai_proxy", display_name="OpenAI Proxy", advanced=True),
50
+ FloatInput(name="request_timeout", display_name="Request Timeout", advanced=True),
51
+ BoolInput(name="show_progress_bar", display_name="Show Progress Bar", advanced=True),
52
+ BoolInput(name="skip_empty", display_name="Skip Empty", advanced=True),
53
+ MessageTextInput(
54
+ name="tiktoken_model_name",
55
+ display_name="TikToken Model Name",
56
+ advanced=True,
57
+ ),
58
+ BoolInput(
59
+ name="tiktoken_enable",
60
+ display_name="TikToken Enable",
61
+ advanced=True,
62
+ value=True,
63
+ info="If False, you must have transformers installed.",
64
+ ),
65
+ IntInput(
66
+ name="dimensions",
67
+ display_name="Dimensions",
68
+ info="The number of dimensions the resulting output embeddings should have. "
69
+ "Only supported by certain models.",
70
+ advanced=True,
71
+ ),
72
+ ]
73
+
74
+ def build_embeddings(self) -> Embeddings:
75
+ return OpenAIEmbeddings(
76
+ client=self.client or None,
77
+ model=self.model,
78
+ dimensions=self.dimensions or None,
79
+ deployment=self.deployment or None,
80
+ api_version=self.openai_api_version or None,
81
+ base_url=self.openai_api_base or None,
82
+ openai_api_type=self.openai_api_type or None,
83
+ openai_proxy=self.openai_proxy or None,
84
+ embedding_ctx_length=self.embedding_ctx_length,
85
+ api_key=self.openai_api_key or None,
86
+ organization=self.openai_organization or None,
87
+ allowed_special="all",
88
+ disallowed_special="all",
89
+ chunk_size=self.chunk_size,
90
+ max_retries=self.max_retries,
91
+ timeout=self.request_timeout or None,
92
+ tiktoken_enabled=self.tiktoken_enable,
93
+ tiktoken_model_name=self.tiktoken_model_name or None,
94
+ show_progress_bar=self.show_progress_bar,
95
+ model_kwargs=self.model_kwargs,
96
+ skip_empty=self.skip_empty,
97
+ default_headers=self.default_headers or None,
98
+ default_query=self.default_query or None,
99
+ )
@@ -0,0 +1,176 @@
1
+ from typing import Any
2
+
3
+ from langchain_openai import ChatOpenAI
4
+ from lfx.base.models.model import LCModelComponent
5
+ from lfx.base.models.openai_constants import OPENAI_CHAT_MODEL_NAMES, OPENAI_REASONING_MODEL_NAMES
6
+ from lfx.field_typing import LanguageModel
7
+ from lfx.field_typing.range_spec import RangeSpec
8
+ from lfx.inputs.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput
9
+ from lfx.log.logger import logger
10
+ from lfx.utils.secrets import secret_value_to_str
11
+
12
+
13
+ class OpenAIModelComponent(LCModelComponent):
14
+ display_name = "OpenAI"
15
+ description = "Generates text using OpenAI LLMs."
16
+ icon = "OpenAI"
17
+ name = "OpenAIModel"
18
+
19
+ inputs = [
20
+ *LCModelComponent.get_base_inputs(),
21
+ IntInput(
22
+ name="max_tokens",
23
+ display_name="Max Tokens",
24
+ advanced=True,
25
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
26
+ range_spec=RangeSpec(min=0, max=128000),
27
+ ),
28
+ DictInput(
29
+ name="model_kwargs",
30
+ display_name="Model Kwargs",
31
+ advanced=True,
32
+ info="Additional keyword arguments to pass to the model.",
33
+ ),
34
+ BoolInput(
35
+ name="json_mode",
36
+ display_name="JSON Mode",
37
+ advanced=True,
38
+ info="If True, it will output JSON regardless of passing a schema.",
39
+ ),
40
+ DropdownInput(
41
+ name="model_name",
42
+ display_name="Model Name",
43
+ advanced=False,
44
+ options=OPENAI_CHAT_MODEL_NAMES + OPENAI_REASONING_MODEL_NAMES,
45
+ value=OPENAI_CHAT_MODEL_NAMES[0],
46
+ combobox=True,
47
+ real_time_refresh=True,
48
+ ),
49
+ StrInput(
50
+ name="openai_api_base",
51
+ display_name="OpenAI API Base",
52
+ advanced=True,
53
+ info="The base URL of the OpenAI API. "
54
+ "Defaults to https://api.openai.com/v1. "
55
+ "You can change this to use other APIs like JinaChat, LocalAI and Prem.",
56
+ ),
57
+ SecretStrInput(
58
+ name="api_key",
59
+ display_name="OpenAI API Key",
60
+ info="The OpenAI API Key to use for the OpenAI model.",
61
+ advanced=False,
62
+ value="OPENAI_API_KEY",
63
+ required=True,
64
+ ),
65
+ SliderInput(
66
+ name="temperature",
67
+ display_name="Temperature",
68
+ value=0.1,
69
+ range_spec=RangeSpec(min=0, max=1, step=0.01),
70
+ show=True,
71
+ ),
72
+ IntInput(
73
+ name="seed",
74
+ display_name="Seed",
75
+ info="The seed controls the reproducibility of the job.",
76
+ advanced=True,
77
+ value=1,
78
+ ),
79
+ IntInput(
80
+ name="max_retries",
81
+ display_name="Max Retries",
82
+ info="The maximum number of retries to make when generating.",
83
+ advanced=True,
84
+ value=5,
85
+ ),
86
+ IntInput(
87
+ name="timeout",
88
+ display_name="Timeout",
89
+ info="The timeout for requests to OpenAI completion API.",
90
+ advanced=True,
91
+ value=700,
92
+ ),
93
+ ]
94
+
95
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
96
+ logger.debug(f"Executing request with model: {self.model_name}")
97
+ logger.debug(f"API key type: {type(self.api_key)}, value: {'***' if self.api_key else None}")
98
+ api_key_value = secret_value_to_str(self.api_key) if self.api_key else None
99
+ logger.debug(f"Final api_key_value type: {type(api_key_value)}, value: {'***' if api_key_value else None}")
100
+
101
+ # Handle model_kwargs and ensure api_key doesn't conflict
102
+ model_kwargs = self.model_kwargs or {}
103
+ # Remove api_key from model_kwargs if it exists to prevent conflicts
104
+ if "api_key" in model_kwargs:
105
+ logger.warning("api_key found in model_kwargs, removing to prevent conflicts")
106
+ model_kwargs = dict(model_kwargs) # Make a copy
107
+ del model_kwargs["api_key"]
108
+
109
+ parameters = {
110
+ "api_key": api_key_value,
111
+ "model_name": self.model_name,
112
+ "max_tokens": self.max_tokens or None,
113
+ "model_kwargs": model_kwargs,
114
+ "base_url": self.openai_api_base or "https://api.openai.com/v1",
115
+ "max_retries": self.max_retries,
116
+ "timeout": self.timeout,
117
+ }
118
+
119
+ # TODO: Revisit if/once parameters are supported for reasoning models
120
+ unsupported_params_for_reasoning_models = ["temperature", "seed"]
121
+
122
+ if self.model_name not in OPENAI_REASONING_MODEL_NAMES:
123
+ parameters["temperature"] = self.temperature if self.temperature is not None else 0.1
124
+ parameters["seed"] = self.seed
125
+ else:
126
+ params_str = ", ".join(unsupported_params_for_reasoning_models)
127
+ logger.debug(f"{self.model_name} is a reasoning model, {params_str} are not configurable. Ignoring.")
128
+
129
+ parameters["stream_usage"] = True
130
+ output = ChatOpenAI(**parameters)
131
+ if self.json_mode:
132
+ output = output.bind(response_format={"type": "json_object"})
133
+
134
+ return output
135
+
136
+ def _get_exception_message(self, e: Exception):
137
+ """Get a message from an OpenAI exception.
138
+
139
+ Args:
140
+ e (Exception): The exception to get the message from.
141
+
142
+ Returns:
143
+ str: The message from the exception.
144
+ """
145
+ try:
146
+ from openai import BadRequestError, NotFoundError
147
+ except ImportError:
148
+ return None
149
+ if isinstance(e, NotFoundError):
150
+ body = getattr(e, "body", None) or {}
151
+ if isinstance(body, dict) and body.get("code") == "model_not_found":
152
+ return (
153
+ f"Model '{self.model_name}' is not available for this OpenAI account. "
154
+ "Your API tier may not have access yet — check "
155
+ "https://platform.openai.com/settings/organization/limits "
156
+ "or select a different model."
157
+ )
158
+ if isinstance(e, BadRequestError):
159
+ message = e.body.get("message")
160
+ if message:
161
+ return message
162
+ return None
163
+
164
+ def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:
165
+ if field_name in {"base_url", "model_name", "api_key"} and field_value in OPENAI_REASONING_MODEL_NAMES:
166
+ build_config["temperature"]["show"] = False
167
+ build_config["seed"]["show"] = False
168
+ # Hide system_message for o1 models - currently unsupported
169
+ if field_value.startswith("o1") and "system_message" in build_config:
170
+ build_config["system_message"]["show"] = False
171
+ if field_name in {"base_url", "model_name", "api_key"} and field_value in OPENAI_CHAT_MODEL_NAMES:
172
+ build_config["temperature"]["show"] = True
173
+ build_config["seed"]["show"] = True
174
+ if "system_message" in build_config:
175
+ build_config["system_message"]["show"] = True
176
+ return build_config
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://schemas.langflow.org/extension/v1.json",
3
+ "id": "lfx-openai",
4
+ "version": "0.1.0",
5
+ "name": "OpenAI",
6
+ "description": "OpenAI component(s) as a standalone Langflow Extension Bundle.",
7
+ "lfx": {
8
+ "compat": ["1"]
9
+ },
10
+ "bundles": [
11
+ {
12
+ "name": "openai",
13
+ "path": "components/openai"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: lfx-openai
3
+ Version: 0.1.0
4
+ Summary: OpenAI component(s) as a standalone Langflow Extension Bundle.
5
+ Project-URL: Homepage, https://github.com/langflow-ai/langflow
6
+ Project-URL: Documentation, https://docs.langflow.org/extensions
7
+ Project-URL: Repository, https://github.com/langflow-ai/langflow
8
+ Author-email: Langflow <contact@langflow.org>
9
+ License: MIT
10
+ Keywords: bundle,extension,langflow,lfx,openai
11
+ Requires-Python: <3.15,>=3.10
12
+ Requires-Dist: langchain-openai>=1.1.6
13
+ Requires-Dist: lfx<2.0.0,>=1.11.0.dev0
14
+ Requires-Dist: openai<3.0.0,>=1.68.2
15
+ Description-Content-Type: text/markdown
16
+
17
+ # lfx-openai
18
+
19
+ OpenAI component(s) as a standalone Langflow Extension Bundle.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install lfx-openai
25
+ ```
26
+
27
+ The bundle is registered automatically via the `langflow.extensions`
28
+ entry-point. After install, restart your Langflow server; the bundle's
29
+ components will appear in the palette under the `openai` group with
30
+ the namespaced IDs `ext:openai:<Class>@official`.
31
+
32
+ ## Develop
33
+
34
+ ```bash
35
+ cd src/bundles/openai
36
+ pip install -e .
37
+ lfx extension validate src/lfx_openai
38
+ ```
39
+
40
+ ## Migration
41
+
42
+ Saved flows referencing the legacy class name(s) or the old import paths
43
+ under `lfx.components.openai.*` are rewritten to the new namespaced
44
+ IDs by the migration table in
45
+ `src/lfx/src/lfx/extension/migration/migration_table.json`.
@@ -0,0 +1,10 @@
1
+ lfx_openai/__init__.py,sha256=rEDy9B4Vr0kYT-XOoZ8t8xz281gYPFkJUuPTj4DSTFQ,494
2
+ lfx_openai/extension.json,sha256=NjSx8mJIL34OmeyxbdAH7eOrQV33COvwlG0a3Wc2hSU,339
3
+ lfx_openai/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ lfx_openai/components/openai/__init__.py,sha256=-nelaqD6W1wIYNpTI426J6OmsRYxnUTfmpEu1q_VzQ8,1322
5
+ lfx_openai/components/openai/openai.py,sha256=o-hNW7IwCTbUSCzxLx7daDWhGRQd94uBDKhMuU2PEto,4504
6
+ lfx_openai/components/openai/openai_chat_model.py,sha256=TUmgkZQYvwFlXZRsbzEcS4TQ1PCGvgvOv4fel_XN6Xc,7154
7
+ lfx_openai-0.1.0.dist-info/METADATA,sha256=gRQIswpxZh-EWFIFWau4jHEpP8mxcUZDg-9Ru15FkDQ,1344
8
+ lfx_openai-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
9
+ lfx_openai-0.1.0.dist-info/entry_points.txt,sha256=CW2uszG9B-E9USfGyobO0iltkzr2R6or65uVPYSx6-I,46
10
+ lfx_openai-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [langflow.extensions]
2
+ lfx-openai = lfx_openai