griptape-nodes 0.64.10__py3-none-any.whl → 0.65.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.
- griptape_nodes/app/app.py +25 -5
- griptape_nodes/cli/commands/init.py +65 -54
- griptape_nodes/cli/commands/libraries.py +92 -85
- griptape_nodes/cli/commands/self.py +121 -0
- griptape_nodes/common/node_executor.py +2142 -101
- griptape_nodes/exe_types/base_iterative_nodes.py +1004 -0
- griptape_nodes/exe_types/connections.py +114 -19
- griptape_nodes/exe_types/core_types.py +225 -7
- griptape_nodes/exe_types/flow.py +3 -3
- griptape_nodes/exe_types/node_types.py +681 -225
- griptape_nodes/exe_types/param_components/README.md +414 -0
- griptape_nodes/exe_types/param_components/api_key_provider_parameter.py +200 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +2 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +79 -5
- griptape_nodes/exe_types/param_types/parameter_button.py +443 -0
- griptape_nodes/machines/control_flow.py +77 -38
- griptape_nodes/machines/dag_builder.py +148 -70
- griptape_nodes/machines/parallel_resolution.py +61 -35
- griptape_nodes/machines/sequential_resolution.py +11 -113
- griptape_nodes/retained_mode/events/app_events.py +1 -0
- griptape_nodes/retained_mode/events/base_events.py +16 -13
- griptape_nodes/retained_mode/events/connection_events.py +3 -0
- griptape_nodes/retained_mode/events/execution_events.py +35 -0
- griptape_nodes/retained_mode/events/flow_events.py +15 -2
- griptape_nodes/retained_mode/events/library_events.py +347 -0
- griptape_nodes/retained_mode/events/node_events.py +48 -0
- griptape_nodes/retained_mode/events/os_events.py +86 -3
- griptape_nodes/retained_mode/events/project_events.py +15 -1
- griptape_nodes/retained_mode/events/workflow_events.py +48 -1
- griptape_nodes/retained_mode/griptape_nodes.py +6 -2
- griptape_nodes/retained_mode/managers/config_manager.py +10 -8
- griptape_nodes/retained_mode/managers/event_manager.py +168 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py +43 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +664 -123
- griptape_nodes/retained_mode/managers/library_manager.py +1143 -139
- griptape_nodes/retained_mode/managers/model_manager.py +2 -3
- griptape_nodes/retained_mode/managers/node_manager.py +148 -25
- griptape_nodes/retained_mode/managers/object_manager.py +3 -1
- griptape_nodes/retained_mode/managers/operation_manager.py +3 -1
- griptape_nodes/retained_mode/managers/os_manager.py +1158 -122
- griptape_nodes/retained_mode/managers/secrets_manager.py +2 -3
- griptape_nodes/retained_mode/managers/settings.py +21 -1
- griptape_nodes/retained_mode/managers/sync_manager.py +2 -3
- griptape_nodes/retained_mode/managers/workflow_manager.py +358 -104
- griptape_nodes/retained_mode/retained_mode.py +3 -3
- griptape_nodes/traits/button.py +44 -2
- griptape_nodes/traits/file_system_picker.py +2 -2
- griptape_nodes/utils/file_utils.py +101 -0
- griptape_nodes/utils/git_utils.py +1226 -0
- griptape_nodes/utils/library_utils.py +122 -0
- {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/METADATA +2 -1
- {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/RECORD +55 -47
- {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.64.10.dist-info → griptape_nodes-0.65.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Using ApiKeyProviderParameter Component
|
|
2
|
+
|
|
3
|
+
The `ApiKeyProviderParameter` component provides a reusable way to add user-provided API key support to nodes that use the Griptape Cloud proxy API. When users provide their own API key, it's automatically forwarded to the provider via the `X-GTC-PROXY-AUTH-API-KEY` header.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This component automatically adds:
|
|
8
|
+
|
|
9
|
+
- A toggle parameter to enable user-provided API key (always uses proxy API)
|
|
10
|
+
- A button on the toggle to open secrets settings (filtered to the relevant API key)
|
|
11
|
+
- An informational message that shows/hides based on whether the user API key is set
|
|
12
|
+
- Helper methods to validate and retrieve API keys
|
|
13
|
+
|
|
14
|
+
**Key Simplification:** All requests go through the Griptape Cloud proxy API. When a user provides their own API key, it's simply forwarded via the `X-GTC-PROXY-AUTH-API-KEY` header - no need to switch between different API endpoints or configurations!
|
|
15
|
+
|
|
16
|
+
## Example: Flux Image Generation Node
|
|
17
|
+
|
|
18
|
+
The `FluxImageGeneration` node demonstrates a complete implementation. Here's how it works:
|
|
19
|
+
|
|
20
|
+
### Step 1: Import the Component
|
|
21
|
+
|
|
22
|
+
Add this import at the top of your node file:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from griptape_nodes.exe_types.param_components.api_key_provider_parameter import ApiKeyProviderParameter
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**File location:** Add this to your imports section (usually near the top with other imports)
|
|
29
|
+
|
|
30
|
+
### Step 2: Define API Key Configuration Constants
|
|
31
|
+
|
|
32
|
+
Define class-level constants for your API key information. These will be used when initializing the component:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
class YourNode(SuccessFailureNode):
|
|
36
|
+
# API key configuration
|
|
37
|
+
USER_API_KEY_NAME = "YOUR_API_KEY_NAME" # e.g., "BFL_API_KEY", "OPENAI_API_KEY"
|
|
38
|
+
USER_API_KEY_URL = "https://example.com/api/keys" # URL where users can get their API key
|
|
39
|
+
USER_API_KEY_PROVIDER_NAME = "Your Provider Name" # e.g., "BlackForest Labs", "OpenAI"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**File location:** Add these as class attributes, typically near the top of your class definition (after `SERVICE_NAME` and `API_KEY_NAME` if you have them)
|
|
43
|
+
|
|
44
|
+
### Step 3: Initialize the Component in `__init__`
|
|
45
|
+
|
|
46
|
+
In your node's `__init__` method, create and initialize the component:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
50
|
+
super().__init__(**kwargs)
|
|
51
|
+
# ... your other initialization code ...
|
|
52
|
+
|
|
53
|
+
# Add API key provider component
|
|
54
|
+
self._api_key_provider = ApiKeyProviderParameter(
|
|
55
|
+
node=self,
|
|
56
|
+
api_key_name=self.USER_API_KEY_NAME,
|
|
57
|
+
provider_name=self.USER_API_KEY_PROVIDER_NAME,
|
|
58
|
+
api_key_url=self.USER_API_KEY_URL,
|
|
59
|
+
)
|
|
60
|
+
self._api_key_provider.add_parameters()
|
|
61
|
+
|
|
62
|
+
# ... rest of your initialization ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**File location:** Add this code in your `__init__` method, typically after any base class initialization and before adding other parameters
|
|
66
|
+
|
|
67
|
+
### Step 4: Override `after_value_set` Method
|
|
68
|
+
|
|
69
|
+
Add this method to handle visibility updates when the API key provider toggle changes:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
73
|
+
self._api_key_provider.after_value_set(parameter, value)
|
|
74
|
+
return super().after_value_set(parameter, value)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**File location:** Add this method to your node class, typically near other lifecycle methods like `_process` or `_validate_api_key`
|
|
78
|
+
|
|
79
|
+
### Step 5: Use the Component in Your Processing Logic
|
|
80
|
+
|
|
81
|
+
In your `_process` method (or wherever you need to get the API key), use the component's validation method:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
def _process(self) -> None:
|
|
85
|
+
# ... your setup code ...
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
validation_result = self._api_key_provider.validate_api_key()
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
self._set_status_results(was_successful=False, result_details=str(e))
|
|
91
|
+
self._handle_failure_exception(e)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Build headers: always use proxy API key, optionally add user API key
|
|
95
|
+
headers = {
|
|
96
|
+
"Authorization": f"Bearer {validation_result.proxy_api_key}",
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
}
|
|
99
|
+
if validation_result.user_api_key:
|
|
100
|
+
headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
|
|
101
|
+
|
|
102
|
+
# Use headers in your API calls - always goes through proxy API
|
|
103
|
+
# ... rest of your processing ...
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**File location:** Add this at the beginning of your `_process` method, before making any API calls
|
|
107
|
+
|
|
108
|
+
### Step 6: (Optional) Create a Helper Method
|
|
109
|
+
|
|
110
|
+
If you had an existing `_validate_api_key` method, you can simplify it to delegate to the component:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
def _validate_api_key(self) -> ApiKeyValidationResult:
|
|
114
|
+
"""Validate and return API keys for proxy API usage.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
ApiKeyValidationResult: Named tuple containing proxy_api_key and optional user_api_key
|
|
118
|
+
"""
|
|
119
|
+
return self._api_key_provider.validate_api_key()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**File location:** Replace your existing `_validate_api_key` method with this, or add it if you don't have one. Don't forget to import `ApiKeyValidationResult`:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from griptape_nodes.exe_types.param_components.api_key_provider_parameter import (
|
|
126
|
+
ApiKeyProviderParameter,
|
|
127
|
+
ApiKeyValidationResult,
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Complete Example
|
|
132
|
+
|
|
133
|
+
Here's a complete minimal example showing all the pieces together:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from __future__ import annotations
|
|
137
|
+
|
|
138
|
+
from typing import Any
|
|
139
|
+
|
|
140
|
+
from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
|
|
141
|
+
from griptape_nodes.exe_types.node_types import SuccessFailureNode
|
|
142
|
+
from griptape_nodes.exe_types.param_components.api_key_provider_parameter import ApiKeyProviderParameter
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ExampleNode(SuccessFailureNode):
|
|
146
|
+
"""Example node with API key provider switching."""
|
|
147
|
+
|
|
148
|
+
# API key configuration
|
|
149
|
+
USER_API_KEY_NAME = "EXAMPLE_API_KEY"
|
|
150
|
+
USER_API_KEY_URL = "https://example.com/api/keys"
|
|
151
|
+
USER_API_KEY_PROVIDER_NAME = "Example Provider"
|
|
152
|
+
|
|
153
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
154
|
+
super().__init__(**kwargs)
|
|
155
|
+
self.category = "API Nodes"
|
|
156
|
+
self.description = "Example node with API key switching"
|
|
157
|
+
|
|
158
|
+
# Add API key provider component
|
|
159
|
+
self._api_key_provider = ApiKeyProviderParameter(
|
|
160
|
+
node=self,
|
|
161
|
+
api_key_name=self.USER_API_KEY_NAME,
|
|
162
|
+
provider_name=self.USER_API_KEY_PROVIDER_NAME,
|
|
163
|
+
api_key_url=self.USER_API_KEY_URL,
|
|
164
|
+
)
|
|
165
|
+
self._api_key_provider.add_parameters()
|
|
166
|
+
|
|
167
|
+
# Add your other parameters here
|
|
168
|
+
self.add_parameter(
|
|
169
|
+
Parameter(
|
|
170
|
+
name="input_text",
|
|
171
|
+
type="str",
|
|
172
|
+
tooltip="Input text",
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
177
|
+
self._api_key_provider.after_value_set(parameter, value)
|
|
178
|
+
return super().after_value_set(parameter, value)
|
|
179
|
+
|
|
180
|
+
def _process(self) -> None:
|
|
181
|
+
# Get API keys for proxy API usage
|
|
182
|
+
try:
|
|
183
|
+
validation_result = self._api_key_provider.validate_api_key()
|
|
184
|
+
except ValueError as e:
|
|
185
|
+
self._set_status_results(was_successful=False, result_details=str(e))
|
|
186
|
+
self._handle_failure_exception(e)
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
# Build headers: always use proxy API key, optionally add user API key
|
|
190
|
+
headers = {
|
|
191
|
+
"Authorization": f"Bearer {validation_result.proxy_api_key}",
|
|
192
|
+
"Content-Type": "application/json",
|
|
193
|
+
}
|
|
194
|
+
if validation_result.user_api_key:
|
|
195
|
+
headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
|
|
196
|
+
|
|
197
|
+
# Always use proxy API - user's key is forwarded via header if provided
|
|
198
|
+
input_text = self.get_parameter_value("input_text")
|
|
199
|
+
result = self._call_proxy_api(headers, input_text)
|
|
200
|
+
|
|
201
|
+
# Process result...
|
|
202
|
+
self._set_status_results(was_successful=True, result_details="Success")
|
|
203
|
+
|
|
204
|
+
def _call_proxy_api(self, headers: dict[str, str], input_text: str) -> dict:
|
|
205
|
+
# Your proxy API call logic here - always uses proxy endpoint
|
|
206
|
+
# User's API key is automatically forwarded if provided
|
|
207
|
+
pass
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Component API Reference
|
|
211
|
+
|
|
212
|
+
### Initialization Parameters
|
|
213
|
+
|
|
214
|
+
When creating an `ApiKeyProviderParameter` instance, you must provide:
|
|
215
|
+
|
|
216
|
+
- **`node`** (BaseNode): The node instance to add parameters to
|
|
217
|
+
- **`api_key_name`** (str): The name of the user's API key secret (e.g., `"BFL_API_KEY"`)
|
|
218
|
+
- **`provider_name`** (str): The display name of the API provider (e.g., `"BlackForest Labs"`)
|
|
219
|
+
- **`api_key_url`** (str): The URL where users can obtain their API key
|
|
220
|
+
|
|
221
|
+
Optional parameters:
|
|
222
|
+
|
|
223
|
+
- **`parameter_name`** (str, default: `"api_key_provider"`): Name for the toggle parameter
|
|
224
|
+
- **`proxy_api_key_name`** (str, default: `"GT_CLOUD_API_KEY"`): Name of the proxy API key secret
|
|
225
|
+
- **`on_label`** (str, default: `"Customer"`): Label when user API is enabled
|
|
226
|
+
- **`off_label`** (str, default: `"Griptape"`): Label when proxy API is enabled
|
|
227
|
+
|
|
228
|
+
### Available Methods
|
|
229
|
+
|
|
230
|
+
#### `add_parameters()`
|
|
231
|
+
|
|
232
|
+
Adds the toggle parameter and message to the node. Call this once in `__init__`.
|
|
233
|
+
|
|
234
|
+
#### `after_value_set(parameter: Parameter, value: Any)`
|
|
235
|
+
|
|
236
|
+
Handles visibility updates when the toggle changes. Call this from your node's `after_value_set` method.
|
|
237
|
+
|
|
238
|
+
#### `validate_api_key() -> ApiKeyValidationResult`
|
|
239
|
+
|
|
240
|
+
Validates and returns API keys for proxy API usage. Returns a `ApiKeyValidationResult` named tuple with:
|
|
241
|
+
|
|
242
|
+
- `proxy_api_key` (str): The Griptape Cloud API key for Authorization header (always required)
|
|
243
|
+
- `user_api_key` (str | None): Optional user-provided API key for X-GTC-PROXY-AUTH-API-KEY header (None if not enabled)
|
|
244
|
+
|
|
245
|
+
**Raises:** `ValueError` if the proxy API key is not set, or if user API is enabled but user key is not set.
|
|
246
|
+
|
|
247
|
+
#### `is_user_api_enabled() -> bool`
|
|
248
|
+
|
|
249
|
+
Checks if user API is currently enabled.
|
|
250
|
+
|
|
251
|
+
#### `get_api_key(use_user_api: bool) -> str`
|
|
252
|
+
|
|
253
|
+
Gets the API key for the specified mode.
|
|
254
|
+
|
|
255
|
+
**Raises:** `ValueError` if the API key is not set.
|
|
256
|
+
|
|
257
|
+
#### `check_api_key_set(api_key: str) -> bool`
|
|
258
|
+
|
|
259
|
+
Checks if an API key exists and is not empty.
|
|
260
|
+
|
|
261
|
+
## Migration Guide: Converting Existing Nodes
|
|
262
|
+
|
|
263
|
+
If you have an existing node that manually handles API key switching, here's how to migrate:
|
|
264
|
+
|
|
265
|
+
### Before (Manual Implementation)
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
269
|
+
super().__init__(**kwargs)
|
|
270
|
+
|
|
271
|
+
# Manual toggle parameter
|
|
272
|
+
self.add_parameter(
|
|
273
|
+
ParameterBool(
|
|
274
|
+
name="api_key_provider",
|
|
275
|
+
default_value=False,
|
|
276
|
+
# ... lots of configuration ...
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Manual message
|
|
281
|
+
self.add_node_element(
|
|
282
|
+
ParameterMessage(
|
|
283
|
+
name="set_api_key",
|
|
284
|
+
# ... lots of configuration ...
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
289
|
+
if parameter.name == "api_key_provider":
|
|
290
|
+
# Manual visibility logic
|
|
291
|
+
if value:
|
|
292
|
+
if not self.check_api_key_set(self.USER_API_KEY_NAME):
|
|
293
|
+
self.show_message_by_name("set_api_key")
|
|
294
|
+
else:
|
|
295
|
+
self.hide_message_by_name("set_api_key")
|
|
296
|
+
return super().after_value_set(parameter, value)
|
|
297
|
+
|
|
298
|
+
def _validate_api_key(self) -> tuple[str, bool]:
|
|
299
|
+
use_user_api = self.get_parameter_value("api_key_provider") or False
|
|
300
|
+
api_key_name = "USER_KEY" if use_user_api else "GT_CLOUD_API_KEY"
|
|
301
|
+
api_key = GriptapeNodes.SecretsManager().get_secret(api_key_name)
|
|
302
|
+
# ... validation logic ...
|
|
303
|
+
return api_key, use_user_api
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### After (Using Component)
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from griptape_nodes.exe_types.param_components.api_key_provider_parameter import (
|
|
310
|
+
ApiKeyProviderParameter,
|
|
311
|
+
ApiKeyValidationResult,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
315
|
+
super().__init__(**kwargs)
|
|
316
|
+
|
|
317
|
+
# Component handles everything
|
|
318
|
+
self._api_key_provider = ApiKeyProviderParameter(
|
|
319
|
+
node=self,
|
|
320
|
+
api_key_name=self.USER_API_KEY_NAME,
|
|
321
|
+
provider_name=self.USER_API_KEY_PROVIDER_NAME,
|
|
322
|
+
api_key_url=self.USER_API_KEY_URL,
|
|
323
|
+
)
|
|
324
|
+
self._api_key_provider.add_parameters()
|
|
325
|
+
|
|
326
|
+
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
327
|
+
self._api_key_provider.after_value_set(parameter, value)
|
|
328
|
+
return super().after_value_set(parameter, value)
|
|
329
|
+
|
|
330
|
+
def _validate_api_key(self) -> ApiKeyValidationResult:
|
|
331
|
+
return self._api_key_provider.validate_api_key()
|
|
332
|
+
|
|
333
|
+
def _process(self) -> None:
|
|
334
|
+
validation_result = self._validate_api_key()
|
|
335
|
+
|
|
336
|
+
# Build headers - always use proxy API
|
|
337
|
+
headers = {
|
|
338
|
+
"Authorization": f"Bearer {validation_result.proxy_api_key}",
|
|
339
|
+
"Content-Type": "application/json",
|
|
340
|
+
}
|
|
341
|
+
if validation_result.user_api_key:
|
|
342
|
+
headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
|
|
343
|
+
|
|
344
|
+
# Always use proxy endpoint
|
|
345
|
+
# ... make API call with headers ...
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Common Patterns
|
|
349
|
+
|
|
350
|
+
### Pattern 1: Simple Proxy API Usage
|
|
351
|
+
|
|
352
|
+
All requests go through the proxy API. User's API key is automatically forwarded if provided:
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
validation_result = self._api_key_provider.validate_api_key()
|
|
356
|
+
|
|
357
|
+
headers = {
|
|
358
|
+
"Authorization": f"Bearer {validation_result.proxy_api_key}",
|
|
359
|
+
"Content-Type": "application/json",
|
|
360
|
+
}
|
|
361
|
+
if validation_result.user_api_key:
|
|
362
|
+
headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
|
|
363
|
+
|
|
364
|
+
# Always use proxy endpoint
|
|
365
|
+
url = urljoin(self._proxy_base, "models/your-model")
|
|
366
|
+
response = await client.post(url, json=payload, headers=headers)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Pattern 2: Logging User API Key Usage
|
|
370
|
+
|
|
371
|
+
You can log when a user-provided API key is being used:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
validation_result = self._api_key_provider.validate_api_key()
|
|
375
|
+
|
|
376
|
+
headers = {
|
|
377
|
+
"Authorization": f"Bearer {validation_result.proxy_api_key}",
|
|
378
|
+
"Content-Type": "application/json",
|
|
379
|
+
}
|
|
380
|
+
if validation_result.user_api_key:
|
|
381
|
+
headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
|
|
382
|
+
self._log("Using user-provided API key via proxy")
|
|
383
|
+
else:
|
|
384
|
+
self._log("Using default proxy API key")
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Troubleshooting
|
|
388
|
+
|
|
389
|
+
### Message Not Showing/Hiding
|
|
390
|
+
|
|
391
|
+
**Problem:** The message doesn't appear when the toggle is switched.
|
|
392
|
+
|
|
393
|
+
**Solution:** Make sure you're calling `self._api_key_provider.after_value_set(parameter, value)` in your node's `after_value_set` method and that it's called before `super().after_value_set()`.
|
|
394
|
+
|
|
395
|
+
### API Key Not Found Error
|
|
396
|
+
|
|
397
|
+
**Problem:** Getting `ValueError` about missing API key even though it's set.
|
|
398
|
+
|
|
399
|
+
**Solution:**
|
|
400
|
+
|
|
401
|
+
1. Verify the API key name matches exactly (case-sensitive)
|
|
402
|
+
1. Check that the secret is set in Settings → Secrets
|
|
403
|
+
1. Ensure you're using the correct `api_key_name` when initializing the component
|
|
404
|
+
|
|
405
|
+
### Toggle Not Appearing
|
|
406
|
+
|
|
407
|
+
**Problem:** The API key provider toggle doesn't appear in the UI.
|
|
408
|
+
|
|
409
|
+
**Solution:** Make sure you're calling `self._api_key_provider.add_parameters()` in your `__init__` method.
|
|
410
|
+
|
|
411
|
+
## See Also
|
|
412
|
+
|
|
413
|
+
- [Flux Image Generation Node](../../libraries/griptape_nodes_library/griptape_nodes_library/image/flux_image_generation.py) - Complete working example
|
|
414
|
+
- [ApiKeyProviderParameter Component](../../src/griptape_nodes/exe_types/param_components/api_key_provider_parameter.py) - Component source code
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""API Key Provider parameter component for reusable API key switching functionality."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, NamedTuple
|
|
4
|
+
|
|
5
|
+
from griptape_nodes.exe_types.core_types import Parameter, ParameterMessage
|
|
6
|
+
from griptape_nodes.exe_types.node_types import BaseNode
|
|
7
|
+
from griptape_nodes.exe_types.param_types.parameter_bool import ParameterBool
|
|
8
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
9
|
+
from griptape_nodes.traits.button import Button
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ApiKeyValidationResult(NamedTuple):
|
|
13
|
+
"""Result from API key validation.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
proxy_api_key: The Griptape Cloud API key for Authorization header (always required)
|
|
17
|
+
user_api_key: Optional user-provided API key for X-GTC-PROXY-AUTH-API-KEY header (None if not enabled)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
proxy_api_key: str
|
|
21
|
+
user_api_key: str | None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ApiKeyProviderParameter:
|
|
25
|
+
"""Reusable component for adding API key provider switching functionality to nodes.
|
|
26
|
+
|
|
27
|
+
This component adds:
|
|
28
|
+
- A toggle parameter to enable user-provided API key (always uses proxy API)
|
|
29
|
+
- A button on the toggle to open secrets settings
|
|
30
|
+
- A message that shows/hides based on whether the user API key is set
|
|
31
|
+
- Helper methods to validate and retrieve API keys
|
|
32
|
+
|
|
33
|
+
Example usage:
|
|
34
|
+
```python
|
|
35
|
+
api_key_provider = ApiKeyProviderParameter(
|
|
36
|
+
node=self,
|
|
37
|
+
api_key_name="BFL_API_KEY",
|
|
38
|
+
provider_name="BlackForest Labs",
|
|
39
|
+
api_key_url="https://dashboard.bfl.ai/api/keys",
|
|
40
|
+
)
|
|
41
|
+
api_key_provider.add_parameters()
|
|
42
|
+
|
|
43
|
+
# In your node's _process method:
|
|
44
|
+
validation_result = api_key_provider.validate_api_key()
|
|
45
|
+
headers = {
|
|
46
|
+
"Authorization": f"Bearer {validation_result.proxy_api_key}",
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
}
|
|
49
|
+
if validation_result.user_api_key:
|
|
50
|
+
headers["X-GTC-PROXY-AUTH-API-KEY"] = validation_result.user_api_key
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
node: The BaseNode instance to add parameters to
|
|
55
|
+
api_key_name: The name of the user's API key secret (e.g., "BFL_API_KEY")
|
|
56
|
+
provider_name: The display name of the API provider (e.g., "BlackForest Labs")
|
|
57
|
+
api_key_url: The URL where users can obtain their API key
|
|
58
|
+
parameter_name: Optional name for the toggle parameter (default: "api_key_provider")
|
|
59
|
+
proxy_api_key_name: Optional name for the proxy API key (default: "GT_CLOUD_API_KEY")
|
|
60
|
+
on_label: Label when user API is enabled (default: "Customer")
|
|
61
|
+
off_label: Label when proxy API is enabled (default: "Griptape")
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__( # noqa: PLR0913
|
|
65
|
+
self,
|
|
66
|
+
node: BaseNode,
|
|
67
|
+
api_key_name: str,
|
|
68
|
+
provider_name: str,
|
|
69
|
+
api_key_url: str,
|
|
70
|
+
*,
|
|
71
|
+
parameter_name: str = "api_key_provider",
|
|
72
|
+
proxy_api_key_name: str = "GT_CLOUD_API_KEY",
|
|
73
|
+
on_label: str = "Customer",
|
|
74
|
+
off_label: str = "Griptape",
|
|
75
|
+
) -> None:
|
|
76
|
+
self._node = node
|
|
77
|
+
self.api_key_name = api_key_name
|
|
78
|
+
self.provider_name = provider_name
|
|
79
|
+
self.api_key_url = api_key_url
|
|
80
|
+
self.parameter_name = parameter_name
|
|
81
|
+
self.proxy_api_key_name = proxy_api_key_name
|
|
82
|
+
self.on_label = on_label
|
|
83
|
+
self.off_label = off_label
|
|
84
|
+
self.message_name = f"{parameter_name}_message"
|
|
85
|
+
|
|
86
|
+
def add_parameters(self) -> None:
|
|
87
|
+
"""Add the API key provider toggle and message to the node."""
|
|
88
|
+
self._node.add_parameter(
|
|
89
|
+
ParameterBool(
|
|
90
|
+
name=self.parameter_name,
|
|
91
|
+
default_value=False,
|
|
92
|
+
tooltip="Use customer API key instead of the default one",
|
|
93
|
+
allow_input=False,
|
|
94
|
+
allow_output=False,
|
|
95
|
+
on_label=self.on_label,
|
|
96
|
+
off_label=self.off_label,
|
|
97
|
+
ui_options={"display_name": "API Key Provider"},
|
|
98
|
+
traits={
|
|
99
|
+
Button(
|
|
100
|
+
icon="key",
|
|
101
|
+
tooltip="Open secrets settings",
|
|
102
|
+
button_link=f"#settings-secrets?filter={self.api_key_name}",
|
|
103
|
+
)
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
self._node.add_node_element(
|
|
108
|
+
ParameterMessage(
|
|
109
|
+
name=self.message_name,
|
|
110
|
+
variant="info",
|
|
111
|
+
title=f"{self.provider_name} API key",
|
|
112
|
+
value=(
|
|
113
|
+
f"To use your own {self.provider_name} API key, visit:\n{self.api_key_url}\n"
|
|
114
|
+
f"to obtain a valid key.\n\n"
|
|
115
|
+
f"Then set {self.api_key_name} in "
|
|
116
|
+
f"[Settings → Secrets](#settings-secrets?filter={self.api_key_name})."
|
|
117
|
+
),
|
|
118
|
+
button_link=f"#settings-secrets?filter={self.api_key_name}",
|
|
119
|
+
button_text="Open Secrets",
|
|
120
|
+
button_icon="key",
|
|
121
|
+
markdown=True,
|
|
122
|
+
hide=True,
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
127
|
+
"""Handle visibility of the API key message when the provider toggle changes."""
|
|
128
|
+
if parameter.name != self.parameter_name:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
if value:
|
|
132
|
+
api_key_value = self.check_api_key_set(self.api_key_name)
|
|
133
|
+
if not api_key_value:
|
|
134
|
+
self._node.show_message_by_name(self.message_name)
|
|
135
|
+
else:
|
|
136
|
+
self._node.hide_message_by_name(self.message_name)
|
|
137
|
+
|
|
138
|
+
def is_user_api_enabled(self) -> bool:
|
|
139
|
+
"""Check if user API key is currently enabled.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
bool: True if user API key is enabled, False otherwise
|
|
143
|
+
"""
|
|
144
|
+
return bool(self._node.get_parameter_value(self.parameter_name) or False)
|
|
145
|
+
|
|
146
|
+
def check_api_key_set(self, api_key: str) -> bool:
|
|
147
|
+
"""Check if an API key exists and is not empty.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
api_key: The name of the API key secret to check
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
bool: True if the API key exists and is not empty, False otherwise
|
|
154
|
+
"""
|
|
155
|
+
api_key_value = GriptapeNodes.SecretsManager().get_secret(api_key)
|
|
156
|
+
if api_key_value is None:
|
|
157
|
+
return False
|
|
158
|
+
if isinstance(api_key_value, str):
|
|
159
|
+
return bool(api_key_value.strip())
|
|
160
|
+
return bool(api_key_value)
|
|
161
|
+
|
|
162
|
+
def get_api_key(self, *, use_user_api: bool) -> str:
|
|
163
|
+
"""Get the API key for the specified mode.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
use_user_api: True to get user API key, False to get proxy API key
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
str: The API key value
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ValueError: If the API key is not set
|
|
173
|
+
"""
|
|
174
|
+
api_key_name = self.api_key_name if use_user_api else self.proxy_api_key_name
|
|
175
|
+
api_key = GriptapeNodes.SecretsManager().get_secret(api_key_name)
|
|
176
|
+
if not api_key:
|
|
177
|
+
msg = f"{self._node.name} is missing {api_key_name}. Ensure it's set in the environment/config."
|
|
178
|
+
raise ValueError(msg)
|
|
179
|
+
return api_key
|
|
180
|
+
|
|
181
|
+
def validate_api_key(self) -> ApiKeyValidationResult:
|
|
182
|
+
"""Validate and return API keys for proxy API usage.
|
|
183
|
+
|
|
184
|
+
Always returns proxy API key. Optionally returns user API key if enabled.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
ApiKeyValidationResult: Named tuple containing proxy_api_key and optional user_api_key
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ValueError: If the proxy API key is not set, or if user API is enabled but user key is not set
|
|
191
|
+
"""
|
|
192
|
+
# Always need proxy API key
|
|
193
|
+
proxy_api_key = self.get_api_key(use_user_api=False)
|
|
194
|
+
|
|
195
|
+
# Optionally get user API key if enabled
|
|
196
|
+
user_api_key = None
|
|
197
|
+
if self.is_user_api_enabled():
|
|
198
|
+
user_api_key = self.get_api_key(use_user_api=True)
|
|
199
|
+
|
|
200
|
+
return ApiKeyValidationResult(proxy_api_key=proxy_api_key, user_api_key=user_api_key)
|