lfx-nightly 0.1.13.dev3__py3-none-any.whl → 0.1.13.dev4__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.

Potentially problematic release.


This version of lfx-nightly might be problematic. Click here for more details.

@@ -284,6 +284,21 @@ class ComposioBaseComponent(Component):
284
284
  # Track all auth field names discovered across all toolkits
285
285
  _all_auth_field_names: set[str] = set()
286
286
 
287
+ @classmethod
288
+ def get_actions_cache(cls) -> dict[str, dict[str, Any]]:
289
+ """Get the class-level actions cache."""
290
+ return cls._actions_cache
291
+
292
+ @classmethod
293
+ def get_action_schema_cache(cls) -> dict[str, dict[str, Any]]:
294
+ """Get the class-level action schema cache."""
295
+ return cls._action_schema_cache
296
+
297
+ @classmethod
298
+ def get_all_auth_field_names(cls) -> set[str]:
299
+ """Get all auth field names discovered across toolkits."""
300
+ return cls._all_auth_field_names
301
+
287
302
  outputs = [
288
303
  Output(name="dataFrame", display_name="DataFrame", method="as_dataframe"),
289
304
  ]
@@ -403,11 +418,11 @@ class ComposioBaseComponent(Component):
403
418
 
404
419
  # Try to load from the class-level cache
405
420
  toolkit_slug = self.app_name.lower()
406
- if toolkit_slug in self.__class__._actions_cache:
421
+ if toolkit_slug in self.__class__.get_actions_cache():
407
422
  # Deep-copy so that any mutation on this instance does not affect the
408
423
  # cached master copy.
409
- self._actions_data = copy.deepcopy(self.__class__._actions_cache[toolkit_slug])
410
- self._action_schemas = copy.deepcopy(self.__class__._action_schema_cache.get(toolkit_slug, {}))
424
+ self._actions_data = copy.deepcopy(self.__class__.get_actions_cache()[toolkit_slug])
425
+ self._action_schemas = copy.deepcopy(self.__class__.get_action_schema_cache().get(toolkit_slug, {}))
411
426
  logger.debug(f"Loaded actions for {toolkit_slug} from in-process cache")
412
427
  return
413
428
 
@@ -630,8 +645,8 @@ class ComposioBaseComponent(Component):
630
645
 
631
646
  # Cache actions for this toolkit so subsequent component instances
632
647
  # can reuse them without hitting the Composio API again.
633
- self.__class__._actions_cache[toolkit_slug] = copy.deepcopy(self._actions_data)
634
- self.__class__._action_schema_cache[toolkit_slug] = copy.deepcopy(self._action_schemas)
648
+ self.__class__.get_actions_cache()[toolkit_slug] = copy.deepcopy(self._actions_data)
649
+ self.__class__.get_action_schema_cache()[toolkit_slug] = copy.deepcopy(self._action_schemas)
635
650
 
636
651
  except ValueError as e:
637
652
  logger.debug(f"Could not populate Composio actions for {self.app_name}: {e}")
@@ -1313,7 +1328,7 @@ class ComposioBaseComponent(Component):
1313
1328
 
1314
1329
  self._auth_dynamic_fields.add(name)
1315
1330
  # Also add to class-level cache for better tracking
1316
- self.__class__._all_auth_field_names.add(name)
1331
+ self.__class__.get_all_auth_field_names().add(name)
1317
1332
 
1318
1333
  def _render_custom_auth_fields(self, build_config: dict, schema: dict[str, Any], mode: str) -> None:
1319
1334
  """Render fields for custom auth based on schema auth_config_details sections."""
@@ -1378,7 +1393,7 @@ class ComposioBaseComponent(Component):
1378
1393
  if name:
1379
1394
  names.add(name)
1380
1395
  # Add to class-level cache for tracking all discovered auth fields
1381
- self.__class__._all_auth_field_names.add(name)
1396
+ self.__class__.get_all_auth_field_names().add(name)
1382
1397
  # Only use names discovered from the toolkit schema; do not add aliases
1383
1398
  return names
1384
1399
 
@@ -1443,7 +1458,7 @@ class ComposioBaseComponent(Component):
1443
1458
  # Check if we need to populate actions - but also check cache availability
1444
1459
  actions_available = bool(self._actions_data)
1445
1460
  toolkit_slug = getattr(self, "app_name", "").lower()
1446
- cached_actions_available = toolkit_slug in self.__class__._actions_cache
1461
+ cached_actions_available = toolkit_slug in self.__class__.get_actions_cache()
1447
1462
 
1448
1463
  should_populate = False
1449
1464
 
@@ -2623,7 +2638,7 @@ class ComposioBaseComponent(Component):
2623
2638
  # Add all dynamic auth fields to protected set
2624
2639
  protected.update(self._auth_dynamic_fields)
2625
2640
  # Also protect any auth fields discovered across all instances
2626
- protected.update(self.__class__._all_auth_field_names)
2641
+ protected.update(self.__class__.get_all_auth_field_names())
2627
2642
 
2628
2643
  for key, cfg in list(build_config.items()):
2629
2644
  if key in protected:
@@ -14,6 +14,7 @@ from lfx.io import (
14
14
  SecretStrInput,
15
15
  StrInput,
16
16
  )
17
+ from lfx.log.logger import logger
17
18
 
18
19
 
19
20
  class AstraDBBaseComponent(Component):
@@ -364,8 +365,8 @@ class AstraDBBaseComponent(Component):
364
365
  "status": db.status if db.status != "ACTIVE" else None,
365
366
  "org_id": db.org_id if db.org_id else None,
366
367
  }
367
- except Exception: # noqa: BLE001
368
- pass
368
+ except Exception as e: # noqa: BLE001
369
+ logger.debug("Failed to get metadata for database %s: %s", db.name, e)
369
370
 
370
371
  return db_info_dict
371
372
 
lfx/base/io/chat.py CHANGED
@@ -6,8 +6,9 @@ class ChatComponent(Component):
6
6
  description = "Use as base for chat components."
7
7
 
8
8
  def get_properties_from_source_component(self):
9
- if hasattr(self, "_vertex") and hasattr(self._vertex, "incoming_edges") and self._vertex.incoming_edges:
10
- source_id = self._vertex.incoming_edges[0].source_id
9
+ vertex = self.get_vertex()
10
+ if vertex and hasattr(vertex, "incoming_edges") and vertex.incoming_edges:
11
+ source_id = vertex.incoming_edges[0].source_id
11
12
  source_vertex = self.graph.get_vertex(source_id)
12
13
  component = source_vertex.custom_component
13
14
  source = component.display_name
@@ -15,6 +16,6 @@ class ChatComponent(Component):
15
16
  possible_attributes = ["model_name", "model_id", "model"]
16
17
  for attribute in possible_attributes:
17
18
  if hasattr(component, attribute) and getattr(component, attribute):
18
- return getattr(component, attribute), icon, source, component._id
19
- return source, icon, component.display_name, component._id
19
+ return getattr(component, attribute), icon, source, component.get_id()
20
+ return source, icon, component.display_name, component.get_id()
20
21
  return None, None, None, None
lfx/base/mcp/util.py CHANGED
@@ -85,6 +85,46 @@ ALLOWED_HEADERS = {
85
85
  }
86
86
 
87
87
 
88
+ def create_mcp_http_client_with_ssl_option(
89
+ headers: dict[str, str] | None = None,
90
+ timeout: httpx.Timeout | None = None,
91
+ auth: httpx.Auth | None = None,
92
+ *,
93
+ verify_ssl: bool = True,
94
+ ) -> httpx.AsyncClient:
95
+ """Create an httpx AsyncClient with configurable SSL verification.
96
+
97
+ This is a custom factory that extends the standard MCP client factory
98
+ to support disabling SSL verification for self-signed certificates.
99
+
100
+ Args:
101
+ headers: Optional headers to include with all requests.
102
+ timeout: Request timeout as httpx.Timeout object.
103
+ auth: Optional authentication handler.
104
+ verify_ssl: Whether to verify SSL certificates (default: True).
105
+
106
+ Returns:
107
+ Configured httpx.AsyncClient instance.
108
+ """
109
+ kwargs: dict[str, Any] = {
110
+ "follow_redirects": True,
111
+ "verify": verify_ssl,
112
+ }
113
+
114
+ if timeout is None:
115
+ kwargs["timeout"] = httpx.Timeout(30.0)
116
+ else:
117
+ kwargs["timeout"] = timeout
118
+
119
+ if headers is not None:
120
+ kwargs["headers"] = headers
121
+
122
+ if auth is not None:
123
+ kwargs["auth"] = auth
124
+
125
+ return httpx.AsyncClient(**kwargs)
126
+
127
+
88
128
  def validate_headers(headers: dict[str, str]) -> dict[str, str]:
89
129
  """Validate and sanitize HTTP headers according to RFC 7230.
90
130
 
@@ -695,7 +735,7 @@ class MCPSessionManager:
695
735
 
696
736
  Args:
697
737
  session_id: Unique identifier for this session
698
- connection_params: Connection parameters including URL, headers, timeouts
738
+ connection_params: Connection parameters including URL, headers, timeouts, verify_ssl
699
739
  preferred_transport: If set to "sse", skip Streamable HTTP and go directly to SSE
700
740
 
701
741
  Returns:
@@ -711,6 +751,19 @@ class MCPSessionManager:
711
751
  # Track which transport succeeded
712
752
  used_transport: list[str] = []
713
753
 
754
+ # Get verify_ssl option from connection params, default to True
755
+ verify_ssl = connection_params.get("verify_ssl", True)
756
+
757
+ # Create custom httpx client factory with SSL verification option
758
+ def custom_httpx_factory(
759
+ headers: dict[str, str] | None = None,
760
+ timeout: httpx.Timeout | None = None,
761
+ auth: httpx.Auth | None = None,
762
+ ) -> httpx.AsyncClient:
763
+ return create_mcp_http_client_with_ssl_option(
764
+ headers=headers, timeout=timeout, auth=auth, verify_ssl=verify_ssl
765
+ )
766
+
714
767
  async def session_task():
715
768
  """Background task that keeps the session alive."""
716
769
  streamable_error = None
@@ -725,6 +778,7 @@ class MCPSessionManager:
725
778
  url=connection_params["url"],
726
779
  headers=connection_params["headers"],
727
780
  timeout=connection_params["timeout_seconds"],
781
+ httpx_client_factory=custom_httpx_factory,
728
782
  ) as (read, write, _):
729
783
  session = ClientSession(read, write)
730
784
  async with session:
@@ -765,6 +819,7 @@ class MCPSessionManager:
765
819
  connection_params["headers"],
766
820
  connection_params["timeout_seconds"],
767
821
  sse_read_timeout,
822
+ httpx_client_factory=custom_httpx_factory,
768
823
  ) as (read, write):
769
824
  session = ClientSession(read, write)
770
825
  async with session:
@@ -1216,6 +1271,8 @@ class MCPStreamableHttpClient:
1216
1271
  headers: dict[str, str] | None = None,
1217
1272
  timeout_seconds: int = 30,
1218
1273
  sse_read_timeout_seconds: int = 30,
1274
+ *,
1275
+ verify_ssl: bool = True,
1219
1276
  ) -> list[StructuredTool]:
1220
1277
  """Connect to MCP server using Streamable HTTP transport with SSE fallback (SDK style)."""
1221
1278
  # Validate and sanitize headers early
@@ -1233,12 +1290,13 @@ class MCPStreamableHttpClient:
1233
1290
  msg = f"Invalid Streamable HTTP or SSE URL ({url}): {error_msg}"
1234
1291
  raise ValueError(msg)
1235
1292
  # Store connection parameters for later use in run_tool
1236
- # Include SSE read timeout for fallback
1293
+ # Include SSE read timeout for fallback and SSL verification option
1237
1294
  self._connection_params = {
1238
1295
  "url": url,
1239
1296
  "headers": validated_headers,
1240
1297
  "timeout_seconds": timeout_seconds,
1241
1298
  "sse_read_timeout_seconds": sse_read_timeout_seconds,
1299
+ "verify_ssl": verify_ssl,
1242
1300
  }
1243
1301
  elif headers:
1244
1302
  self._connection_params["headers"] = validated_headers
@@ -1258,11 +1316,18 @@ class MCPStreamableHttpClient:
1258
1316
  return response.tools
1259
1317
 
1260
1318
  async def connect_to_server(
1261
- self, url: str, headers: dict[str, str] | None = None, sse_read_timeout_seconds: int = 30
1319
+ self,
1320
+ url: str,
1321
+ headers: dict[str, str] | None = None,
1322
+ sse_read_timeout_seconds: int = 30,
1323
+ *,
1324
+ verify_ssl: bool = True,
1262
1325
  ) -> list[StructuredTool]:
1263
1326
  """Connect to MCP server using Streamable HTTP with SSE fallback transport (SDK style)."""
1264
1327
  return await asyncio.wait_for(
1265
- self._connect_to_server(url, headers, sse_read_timeout_seconds=sse_read_timeout_seconds),
1328
+ self._connect_to_server(
1329
+ url, headers, sse_read_timeout_seconds=sse_read_timeout_seconds, verify_ssl=verify_ssl
1330
+ ),
1266
1331
  timeout=get_settings_service().settings.mcp_server_timeout,
1267
1332
  )
1268
1333
 
@@ -1493,7 +1558,8 @@ async def update_tools(
1493
1558
  client = mcp_stdio_client
1494
1559
  elif mode in ["Streamable_HTTP", "SSE"]:
1495
1560
  # Streamable HTTP connection with SSE fallback
1496
- tools = await mcp_streamable_http_client.connect_to_server(url, headers=headers)
1561
+ verify_ssl = server_config.get("verify_ssl", True)
1562
+ tools = await mcp_streamable_http_client.connect_to_server(url, headers=headers, verify_ssl=verify_ssl)
1497
1563
  client = mcp_streamable_http_client
1498
1564
  else:
1499
1565
  logger.error(f"Invalid MCP server mode for '{server_name}': {mode}")
lfx/cli/commands.py CHANGED
@@ -43,7 +43,7 @@ def serve_command(
43
43
  host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host to bind the server to"),
44
44
  port: int = typer.Option(8000, "--port", "-p", help="Port to bind the server to"),
45
45
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Show diagnostic output and execution details"), # noqa: FBT001, FBT003
46
- env_file: Path | None = typer.Option( # noqa: B008
46
+ env_file: Path | None = typer.Option(
47
47
  None,
48
48
  "--env-file",
49
49
  help="Path to the .env file containing environment variables",
@@ -161,7 +161,7 @@ class AgentComponent(ToolCallingAgentComponent):
161
161
  },
162
162
  ],
163
163
  ),
164
- *LCToolsAgentComponent._base_inputs,
164
+ *LCToolsAgentComponent.get_base_inputs(),
165
165
  # removed memory inputs from agent component
166
166
  # *memory_inputs,
167
167
  BoolInput(
@@ -185,7 +185,7 @@ class CugaComponent(ToolCallingAgentComponent):
185
185
  },
186
186
  ],
187
187
  ),
188
- *LCToolsAgentComponent._base_inputs,
188
+ *LCToolsAgentComponent.get_base_inputs(),
189
189
  BoolInput(
190
190
  name="add_current_date_tool",
191
191
  display_name="Current Date",
@@ -61,6 +61,7 @@ class MCPToolsComponent(ComponentWithCache):
61
61
  "mcp_server",
62
62
  "tool",
63
63
  "use_cache",
64
+ "verify_ssl",
64
65
  ]
65
66
 
66
67
  display_name = "MCP Tools"
@@ -86,6 +87,16 @@ class MCPToolsComponent(ComponentWithCache):
86
87
  value=False,
87
88
  advanced=True,
88
89
  ),
90
+ BoolInput(
91
+ name="verify_ssl",
92
+ display_name="Verify SSL Certificate",
93
+ info=(
94
+ "Enable SSL certificate verification for HTTPS connections. "
95
+ "Disable only for development/testing with self-signed certificates."
96
+ ),
97
+ value=True,
98
+ advanced=True,
99
+ ),
89
100
  DropdownInput(
90
101
  name="tool",
91
102
  display_name="Tool",
@@ -210,6 +221,11 @@ class MCPToolsComponent(ComponentWithCache):
210
221
  self.tools = []
211
222
  return [], {"name": server_name, "config": server_config}
212
223
 
224
+ # Add verify_ssl option to server config if not present
225
+ if "verify_ssl" not in server_config:
226
+ verify_ssl = getattr(self, "verify_ssl", True)
227
+ server_config["verify_ssl"] = verify_ssl
228
+
213
229
  _, tool_list, tool_cache = await update_tools(
214
230
  server_name=server_name,
215
231
  server_config=server_config,
@@ -16,7 +16,7 @@ class AmazonBedrockConverseComponent(LCModelComponent):
16
16
  beta = True
17
17
 
18
18
  inputs = [
19
- *LCModelComponent._base_inputs,
19
+ *LCModelComponent.get_base_inputs(),
20
20
  DropdownInput(
21
21
  name="model_id",
22
22
  display_name="Model ID",
@@ -92,7 +92,7 @@ class ApifyActorsComponent(Component):
92
92
  """Run the Actor and return node output."""
93
93
  input_ = json.loads(self.run_input)
94
94
  fields = ApifyActorsComponent.parse_dataset_fields(self.dataset_fields) if self.dataset_fields else None
95
- res = self._run_actor(self.actor_id, input_, fields=fields)
95
+ res = self.run_actor(self.actor_id, input_, fields=fields)
96
96
  if self.flatten_dataset:
97
97
  res = [ApifyActorsComponent.flatten(item) for item in res]
98
98
  data = [Data(data=item) for item in res]
@@ -159,7 +159,7 @@ class ApifyActorsComponent(Component):
159
159
  # retrieve if nested, just in case
160
160
  input_dict = input_dict.get("run_input", input_dict)
161
161
 
162
- res = parent._run_actor(actor_id, input_dict)
162
+ res = parent.run_actor(actor_id, input_dict)
163
163
  return "\n\n".join([ApifyActorsComponent.dict_to_json_str(item) for item in res])
164
164
 
165
165
  return ApifyActorRun
@@ -256,7 +256,7 @@ class ApifyActorsComponent(Component):
256
256
  valid_chars = string.ascii_letters + string.digits + "_-"
257
257
  return "".join(char if char in valid_chars else "_" for char in actor_id)
258
258
 
259
- def _run_actor(self, actor_id: str, run_input: dict, fields: list[str] | None = None) -> list[dict]:
259
+ def run_actor(self, actor_id: str, run_input: dict, fields: list[str] | None = None) -> list[dict]:
260
260
  """Run an Apify Actor and return the output dataset.
261
261
 
262
262
  Args:
@@ -227,7 +227,7 @@ class AstraDBVectorStoreComponent(AstraDBBaseComponent, LCVectorStoreComponent):
227
227
  for provider in providers.reranking_providers.values()
228
228
  for model in provider.models
229
229
  ]
230
- except (AttributeError, KeyError) as e:
230
+ except Exception as e: # noqa: BLE001
231
231
  self.log(f"Hybrid search not available: {e}")
232
232
  return {
233
233
  "available": False,
@@ -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
@@ -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)
@@ -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