alita-sdk 0.3.528__py3-none-any.whl → 0.3.554__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 alita-sdk might be problematic. Click here for more details.
- alita_sdk/community/__init__.py +8 -4
- alita_sdk/configurations/__init__.py +1 -0
- alita_sdk/configurations/openapi.py +111 -0
- alita_sdk/runtime/clients/client.py +185 -10
- alita_sdk/runtime/langchain/langraph_agent.py +2 -2
- alita_sdk/runtime/langchain/utils.py +46 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +2 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/tools.py +76 -9
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/artifact.py +70 -21
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +363 -44
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +7 -2
- alita_sdk/runtime/utils/AlitaCallback.py +2 -1
- alita_sdk/runtime/utils/utils.py +34 -0
- alita_sdk/tools/__init__.py +41 -1
- alita_sdk/tools/ado/work_item/ado_wrapper.py +33 -2
- alita_sdk/tools/base_indexer_toolkit.py +36 -24
- alita_sdk/tools/confluence/api_wrapper.py +5 -6
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/openapi/__init__.py +280 -120
- alita_sdk/tools/openapi/api_wrapper.py +883 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/servicenow/__init__.py +9 -9
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/METADATA +2 -2
- {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/RECORD +46 -33
- {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/top_level.txt +0 -0
|
@@ -1,140 +1,300 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from langchain_core.tools import BaseTool, BaseToolkit
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
4
8
|
import yaml
|
|
5
|
-
from typing import List, Any, Optional, Dict
|
|
6
|
-
from langchain_core.tools import BaseTool, BaseToolkit, ToolException
|
|
7
|
-
from requests_openapi import Operation, Client, Server
|
|
8
9
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
10
|
+
from .api_wrapper import _get_base_url_from_spec, build_wrapper
|
|
11
|
+
from .tool import OpenApiAction
|
|
12
|
+
from ..elitea_base import filter_missconfigured_index_tools
|
|
13
|
+
from ...configurations.openapi import OpenApiConfiguration
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
name = 'openapi'
|
|
13
16
|
|
|
14
|
-
name = "openapi"
|
|
15
17
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if auth_type.lower() == 'bearer':
|
|
23
|
-
headers['Authorization'] = f'Bearer {auth_key}'
|
|
24
|
-
if auth_type.lower() == 'basic':
|
|
25
|
-
headers['Authorization'] = f'Basic {auth_key}'
|
|
26
|
-
if auth_type.lower() == 'custom':
|
|
27
|
-
headers[
|
|
28
|
-
tool["settings"]["authentication"]["settings"]["custom_header_name"]] = f'{auth_key}'
|
|
18
|
+
def get_toolkit(tool) -> BaseToolkit:
|
|
19
|
+
settings = tool.get('settings', {}) or {}
|
|
20
|
+
# Extract selected_tools separately to avoid duplicate keyword argument when unpacking **settings
|
|
21
|
+
selected_tools = settings.get('selected_tools', [])
|
|
22
|
+
# Filter out selected_tools from settings to prevent "got multiple values for keyword argument"
|
|
23
|
+
filtered_settings = {k: v for k, v in settings.items() if k != 'selected_tools'}
|
|
29
24
|
return AlitaOpenAPIToolkit.get_toolkit(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def create_api_tool(name: str, op: Operation):
|
|
36
|
-
fields = {}
|
|
37
|
-
headers = {}
|
|
38
|
-
headers_descriptions = []
|
|
39
|
-
|
|
40
|
-
for parameter in op.spec.parameters:
|
|
41
|
-
if "header" in parameter.param_in:
|
|
42
|
-
headers[parameter.name] = parameter.param_schema.default
|
|
43
|
-
headers_descriptions.append(f"Header: {parameter.name}. Description: {parameter.description}.")
|
|
44
|
-
continue
|
|
45
|
-
fields[parameter.name] = (str, Field(default=parameter.param_schema.default,
|
|
46
|
-
description=parameter.description))
|
|
47
|
-
|
|
48
|
-
# add headers
|
|
49
|
-
if headers:
|
|
50
|
-
fields['headers'] = (Optional[dict], Field(default = headers, description="The dict that represents headers for request:\n" + '\n'.join(headers_descriptions)))
|
|
51
|
-
|
|
52
|
-
if op.spec.requestBody:
|
|
53
|
-
fields['json'] = (Optional[str], Field(default = None, description="JSON request body provided as a string"))
|
|
54
|
-
|
|
55
|
-
op.server = Server.from_openapi_server(op.server) # patch this
|
|
56
|
-
op.server.get_url = partial(Server.get_url, op.server)
|
|
57
|
-
op.server.set_url = partial(Server.set_url, op.server)
|
|
58
|
-
return ApiTool(
|
|
59
|
-
name=name,
|
|
60
|
-
description=op.spec.description if op.spec.description else op.spec.summary,
|
|
61
|
-
args_schema=create_model(
|
|
62
|
-
'request_params',
|
|
63
|
-
regexp = (Optional[str], Field(description="Regular expression used to remove from final output if any", default=None)),
|
|
64
|
-
**fields),
|
|
65
|
-
callable=op
|
|
25
|
+
selected_tools=selected_tools,
|
|
26
|
+
toolkit_name=tool.get('toolkit_name'),
|
|
27
|
+
**filtered_settings,
|
|
66
28
|
)
|
|
67
29
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
30
|
+
|
|
31
|
+
def get_tools(tool):
|
|
32
|
+
return get_toolkit(tool).get_tools()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_toolkit_available_tools(settings: dict) -> dict:
|
|
36
|
+
"""Return instance-dependent tool list + per-tool args JSON schemas.
|
|
37
|
+
|
|
38
|
+
This is used by backend services when the UI needs spec-derived tool names
|
|
39
|
+
and input schemas (one tool per operationId). It must be JSON-serializable.
|
|
40
|
+
"""
|
|
41
|
+
if not isinstance(settings, dict):
|
|
42
|
+
settings = {}
|
|
43
|
+
|
|
44
|
+
# Extract and merge openapi_configuration if present (same pattern as get_toolkit)
|
|
45
|
+
openapi_configuration = settings.get('openapi_configuration') or {}
|
|
46
|
+
if hasattr(openapi_configuration, 'model_dump'):
|
|
47
|
+
openapi_configuration = openapi_configuration.model_dump(mode='json')
|
|
48
|
+
if not isinstance(openapi_configuration, dict):
|
|
49
|
+
openapi_configuration = {}
|
|
50
|
+
|
|
51
|
+
# Merge settings with openapi_configuration so api_key, auth_type etc. are at root level
|
|
52
|
+
merged_settings: Dict[str, Any] = {
|
|
53
|
+
**settings,
|
|
54
|
+
**openapi_configuration,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
spec = merged_settings.get('spec') or merged_settings.get('schema_settings') or merged_settings.get('openapi_spec')
|
|
58
|
+
base_url_override = merged_settings.get('base_url') or merged_settings.get('base_url_override')
|
|
59
|
+
|
|
60
|
+
if not spec or not isinstance(spec, (str, dict)):
|
|
61
|
+
return {"tools": [], "args_schemas": {}, "error": "OpenAPI spec is missing"}
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
headers = _build_headers_from_settings(merged_settings)
|
|
65
|
+
api_wrapper = build_wrapper(
|
|
66
|
+
openapi_spec=spec,
|
|
67
|
+
base_headers=headers,
|
|
68
|
+
base_url_override=base_url_override,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
tool_defs = api_wrapper.get_available_tools(selected_tools=None)
|
|
72
|
+
|
|
73
|
+
tools = []
|
|
74
|
+
args_schemas = {}
|
|
75
|
+
|
|
76
|
+
for tool_def in tool_defs:
|
|
77
|
+
name_val = tool_def.get('name')
|
|
78
|
+
if not isinstance(name_val, str) or not name_val:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
desc_val = tool_def.get('description')
|
|
82
|
+
if not isinstance(desc_val, str):
|
|
83
|
+
desc_val = ''
|
|
84
|
+
|
|
85
|
+
tools.append({"name": name_val, "description": desc_val})
|
|
86
|
+
|
|
87
|
+
args_schema = tool_def.get('args_schema')
|
|
88
|
+
if args_schema is None:
|
|
89
|
+
args_schemas[name_val] = {"type": "object", "properties": {}, "required": []}
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
if hasattr(args_schema, 'model_json_schema'):
|
|
94
|
+
args_schemas[name_val] = args_schema.model_json_schema()
|
|
95
|
+
elif hasattr(args_schema, 'schema'):
|
|
96
|
+
args_schemas[name_val] = args_schema.schema()
|
|
97
|
+
else:
|
|
98
|
+
args_schemas[name_val] = {"type": "object", "properties": {}, "required": []}
|
|
99
|
+
except Exception:
|
|
100
|
+
args_schemas[name_val] = {"type": "object", "properties": {}, "required": []}
|
|
101
|
+
|
|
102
|
+
# Ensure stable JSON-serializability.
|
|
86
103
|
try:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
json.dumps({"tools": tools, "args_schemas": args_schemas})
|
|
105
|
+
except Exception:
|
|
106
|
+
return {"tools": tools, "args_schemas": {}}
|
|
107
|
+
|
|
108
|
+
return {"tools": tools, "args_schemas": args_schemas}
|
|
109
|
+
|
|
110
|
+
except Exception as e: # pylint: disable=W0718
|
|
111
|
+
return {"tools": [], "args_schemas": {}, "error": str(e)}
|
|
91
112
|
|
|
92
113
|
class AlitaOpenAPIToolkit(BaseToolkit):
|
|
93
114
|
request_session: Any #: :meta private:
|
|
94
115
|
tools: List[BaseTool] = []
|
|
95
116
|
|
|
117
|
+
@staticmethod
|
|
118
|
+
def toolkit_config_schema() -> BaseModel:
|
|
119
|
+
# OpenAPI tool names + per-tool args schemas depend on the user-provided spec,
|
|
120
|
+
# so `selected_tools` cannot be an enum here (unlike most toolkits).
|
|
121
|
+
|
|
122
|
+
model = create_model(
|
|
123
|
+
name,
|
|
124
|
+
__config__=ConfigDict(
|
|
125
|
+
extra='ignore',
|
|
126
|
+
json_schema_extra={
|
|
127
|
+
'metadata': {
|
|
128
|
+
'label': 'OpenAPI',
|
|
129
|
+
'icon_url': 'openapi.svg',
|
|
130
|
+
'categories': ['integrations'],
|
|
131
|
+
'extra_categories': ['api', 'openapi', 'swagger'],
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
openapi_configuration=(
|
|
136
|
+
OpenApiConfiguration,
|
|
137
|
+
Field(
|
|
138
|
+
description='OpenAPI credentials configuration',
|
|
139
|
+
json_schema_extra={'configuration_types': ['openapi']},
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
base_url=(
|
|
143
|
+
Optional[str],
|
|
144
|
+
Field(
|
|
145
|
+
default=None,
|
|
146
|
+
description=(
|
|
147
|
+
"Optional base URL override (absolute, starting with http:// or https://). "
|
|
148
|
+
"Use this when your OpenAPI spec has no `servers` entry, or when `servers[0].url` "
|
|
149
|
+
"is not absolute (e.g. '/api/v3'). Example: 'https://petstore3.swagger.io'."
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
spec=(
|
|
154
|
+
str,
|
|
155
|
+
Field(
|
|
156
|
+
description=(
|
|
157
|
+
'OpenAPI specification (URL or raw JSON/YAML text). '
|
|
158
|
+
'Used to generate per-operation tools (one tool per operationId).'
|
|
159
|
+
),
|
|
160
|
+
json_schema_extra={'ui_component': 'openapi_spec'},
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
selected_tools=(
|
|
164
|
+
List[str],
|
|
165
|
+
Field(
|
|
166
|
+
default=[],
|
|
167
|
+
description='Optional list of operationIds to enable. If empty, all operations are enabled.',
|
|
168
|
+
json_schema_extra={'args_schemas': {}},
|
|
169
|
+
),
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
return model
|
|
173
|
+
|
|
96
174
|
@classmethod
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# Try to detect if it's YAML or JSON by attempting to parse as JSON first
|
|
107
|
-
try:
|
|
108
|
-
openapi_spec = json.loads(openapi_spec)
|
|
109
|
-
except json.JSONDecodeError:
|
|
110
|
-
# If JSON parsing fails, try YAML
|
|
111
|
-
try:
|
|
112
|
-
openapi_spec = yaml.safe_load(openapi_spec)
|
|
113
|
-
except yaml.YAMLError as e:
|
|
114
|
-
raise ToolException(f"Failed to parse OpenAPI spec as JSON or YAML: {e}")
|
|
115
|
-
c = Client()
|
|
116
|
-
c.load_spec(openapi_spec)
|
|
117
|
-
if headers:
|
|
118
|
-
c.requestor.headers.update(headers)
|
|
119
|
-
tools = []
|
|
120
|
-
for i in tools_set:
|
|
175
|
+
@filter_missconfigured_index_tools
|
|
176
|
+
def get_toolkit(
|
|
177
|
+
cls,
|
|
178
|
+
selected_tools: list[str] | None = None,
|
|
179
|
+
toolkit_name: Optional[str] = None,
|
|
180
|
+
**kwargs,
|
|
181
|
+
):
|
|
182
|
+
if selected_tools is None:
|
|
183
|
+
selected_tools = []
|
|
121
184
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
185
|
+
tool_names = _coerce_selected_tool_names(selected_tools)
|
|
186
|
+
|
|
187
|
+
openapi_configuration = kwargs.get('openapi_configuration') or {}
|
|
188
|
+
if hasattr(openapi_configuration, 'model_dump'):
|
|
189
|
+
openapi_configuration = openapi_configuration.model_dump(mode='json')
|
|
190
|
+
if not isinstance(openapi_configuration, dict):
|
|
191
|
+
openapi_configuration = {}
|
|
192
|
+
|
|
193
|
+
merged_settings: Dict[str, Any] = {
|
|
194
|
+
**kwargs,
|
|
195
|
+
**openapi_configuration,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
openapi_spec = merged_settings.get('spec') or merged_settings.get('schema_settings') or merged_settings.get('openapi_spec')
|
|
199
|
+
base_url_override = merged_settings.get('base_url') or merged_settings.get('base_url_override')
|
|
200
|
+
headers = _build_headers_from_settings(merged_settings)
|
|
201
|
+
|
|
202
|
+
api_wrapper = build_wrapper(
|
|
203
|
+
openapi_spec=openapi_spec,
|
|
204
|
+
base_headers=headers,
|
|
205
|
+
base_url_override=base_url_override,
|
|
206
|
+
)
|
|
207
|
+
base_url = _get_base_url_from_spec(api_wrapper.spec)
|
|
208
|
+
|
|
209
|
+
tools: List[BaseTool] = []
|
|
210
|
+
for tool_def in api_wrapper.get_available_tools(selected_tools=tool_names):
|
|
211
|
+
description = tool_def.get('description') or ''
|
|
212
|
+
if toolkit_name:
|
|
213
|
+
description = f"{description}\nToolkit: {toolkit_name}"
|
|
214
|
+
if base_url:
|
|
215
|
+
description = f"{description}\nBase URL: {base_url}"
|
|
216
|
+
description = description[:1000]
|
|
217
|
+
|
|
218
|
+
tools.append(
|
|
219
|
+
OpenApiAction(
|
|
220
|
+
api_wrapper=api_wrapper,
|
|
221
|
+
name=tool_def['name'],
|
|
222
|
+
description=description,
|
|
223
|
+
args_schema=tool_def.get('args_schema'),
|
|
224
|
+
metadata={"toolkit_name": toolkit_name} if toolkit_name else {},
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return cls(request_session=api_wrapper, tools=tools)
|
|
138
229
|
|
|
139
230
|
def get_tools(self):
|
|
140
231
|
return self.tools
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _coerce_selected_tool_names(selected_tools: Any) -> list[str]:
|
|
235
|
+
if not selected_tools:
|
|
236
|
+
return []
|
|
237
|
+
|
|
238
|
+
if isinstance(selected_tools, list):
|
|
239
|
+
tool_names: List[str] = []
|
|
240
|
+
for item in selected_tools:
|
|
241
|
+
if isinstance(item, str):
|
|
242
|
+
tool_names.append(item)
|
|
243
|
+
elif isinstance(item, dict):
|
|
244
|
+
name_val = item.get('name')
|
|
245
|
+
if isinstance(name_val, str) and name_val.strip():
|
|
246
|
+
tool_names.append(name_val)
|
|
247
|
+
return [t for t in tool_names if t]
|
|
248
|
+
|
|
249
|
+
return []
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _secret_to_str(value: Any) -> Optional[str]:
|
|
253
|
+
if value is None:
|
|
254
|
+
return None
|
|
255
|
+
if hasattr(value, 'get_secret_value'):
|
|
256
|
+
try:
|
|
257
|
+
value = value.get_secret_value()
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
if isinstance(value, str):
|
|
261
|
+
return value
|
|
262
|
+
return str(value)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _build_headers_from_settings(settings: Dict[str, Any]) -> Dict[str, str]:
|
|
266
|
+
headers: Dict[str, str] = {}
|
|
267
|
+
|
|
268
|
+
# Legacy structure used by the custom OpenAPI UI
|
|
269
|
+
auth = settings.get('authentication')
|
|
270
|
+
if isinstance(auth, dict) and auth.get('type') == 'api_key':
|
|
271
|
+
auth_settings = auth.get('settings') or {}
|
|
272
|
+
if isinstance(auth_settings, dict):
|
|
273
|
+
auth_type = str(auth_settings.get('auth_type', '')).strip().lower()
|
|
274
|
+
api_key = _secret_to_str(auth_settings.get('api_key'))
|
|
275
|
+
if api_key:
|
|
276
|
+
if auth_type == 'bearer':
|
|
277
|
+
headers['Authorization'] = f'Bearer {api_key}'
|
|
278
|
+
elif auth_type == 'basic':
|
|
279
|
+
headers['Authorization'] = f'Basic {api_key}'
|
|
280
|
+
elif auth_type == 'custom':
|
|
281
|
+
header_name = auth_settings.get('custom_header_name')
|
|
282
|
+
if header_name:
|
|
283
|
+
headers[str(header_name)] = f'{api_key}'
|
|
284
|
+
|
|
285
|
+
# New regular-schema structure (GitHub-style sections) uses flattened fields
|
|
286
|
+
if not headers:
|
|
287
|
+
api_key = _secret_to_str(settings.get('api_key'))
|
|
288
|
+
if api_key:
|
|
289
|
+
auth_type = str(settings.get('auth_type', 'Bearer'))
|
|
290
|
+
auth_type_norm = auth_type.strip().lower()
|
|
291
|
+
if auth_type_norm == 'bearer':
|
|
292
|
+
headers['Authorization'] = f'Bearer {api_key}'
|
|
293
|
+
elif auth_type_norm == 'basic':
|
|
294
|
+
headers['Authorization'] = f'Basic {api_key}'
|
|
295
|
+
elif auth_type_norm == 'custom':
|
|
296
|
+
header_name = settings.get('custom_header_name')
|
|
297
|
+
if header_name:
|
|
298
|
+
headers[str(header_name)] = f'{api_key}'
|
|
299
|
+
|
|
300
|
+
return headers
|