fastworkflow 2.15.5__py3-none-any.whl → 2.17.13__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.
- fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +1 -1
- fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +16 -2
- fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +27 -570
- fastworkflow/_workflows/command_metadata_extraction/intent_detection.py +360 -0
- fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py +411 -0
- fastworkflow/chat_session.py +379 -206
- fastworkflow/cli.py +80 -165
- fastworkflow/command_context_model.py +73 -7
- fastworkflow/command_executor.py +14 -5
- fastworkflow/command_metadata_api.py +106 -6
- fastworkflow/examples/fastworkflow.env +2 -1
- fastworkflow/examples/fastworkflow.passwords.env +2 -1
- fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +32 -3
- fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +6 -5
- fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +32 -3
- fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +13 -2
- fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +1 -1
- fastworkflow/intent_clarification_agent.py +131 -0
- fastworkflow/mcp_server.py +3 -3
- fastworkflow/run/__main__.py +33 -40
- fastworkflow/run_fastapi_mcp/README.md +373 -0
- fastworkflow/run_fastapi_mcp/__main__.py +1300 -0
- fastworkflow/run_fastapi_mcp/conversation_store.py +391 -0
- fastworkflow/run_fastapi_mcp/jwt_manager.py +341 -0
- fastworkflow/run_fastapi_mcp/mcp_specific.py +103 -0
- fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py +40 -0
- fastworkflow/run_fastapi_mcp/utils.py +517 -0
- fastworkflow/train/__main__.py +1 -1
- fastworkflow/utils/chat_adapter.py +99 -0
- fastworkflow/utils/python_utils.py +4 -4
- fastworkflow/utils/react.py +258 -0
- fastworkflow/utils/signatures.py +338 -139
- fastworkflow/workflow.py +1 -5
- fastworkflow/workflow_agent.py +185 -133
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/METADATA +16 -18
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/RECORD +40 -30
- fastworkflow/run_agent/__main__.py +0 -294
- fastworkflow/run_agent/agent_module.py +0 -194
- /fastworkflow/{run_agent → run_fastapi_mcp}/__init__.py +0 -0
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/LICENSE +0 -0
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/WHEEL +0 -0
- {fastworkflow-2.15.5.dist-info → fastworkflow-2.17.13.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import sys
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from pydantic_core import PydanticUndefined
|
|
8
|
+
|
|
9
|
+
import fastworkflow
|
|
10
|
+
from fastworkflow.utils.logging import logger
|
|
11
|
+
from fastworkflow import ModuleType
|
|
12
|
+
|
|
13
|
+
from fastworkflow.utils.signatures import InputForParamExtraction
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
INVALID_INT_VALUE = -sys.maxsize
|
|
17
|
+
INVALID_FLOAT_VALUE = -sys.float_info.max
|
|
18
|
+
|
|
19
|
+
MISSING_INFORMATION_ERRMSG = fastworkflow.get_env_var("MISSING_INFORMATION_ERRMSG")
|
|
20
|
+
INVALID_INFORMATION_ERRMSG = fastworkflow.get_env_var("INVALID_INFORMATION_ERRMSG")
|
|
21
|
+
|
|
22
|
+
NOT_FOUND = fastworkflow.get_env_var("NOT_FOUND")
|
|
23
|
+
INVALID = fastworkflow.get_env_var("INVALID")
|
|
24
|
+
PARAMETER_EXTRACTION_ERROR_MSG = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ParameterExtraction:
|
|
28
|
+
class Output(BaseModel):
|
|
29
|
+
parameters_are_valid: bool
|
|
30
|
+
cmd_parameters: Optional[BaseModel] = None
|
|
31
|
+
error_msg: Optional[str] = None
|
|
32
|
+
suggestions: Optional[Dict[str, List[str]]] = None
|
|
33
|
+
|
|
34
|
+
def __init__(self, cme_workflow: fastworkflow.Workflow, app_workflow: fastworkflow.Workflow, command_name: str, command: str):
|
|
35
|
+
self.cme_workflow = cme_workflow
|
|
36
|
+
self.app_workflow = app_workflow
|
|
37
|
+
self.command_name = command_name
|
|
38
|
+
self.command = command
|
|
39
|
+
|
|
40
|
+
def extract(self) -> "ParameterExtraction.Output":
|
|
41
|
+
app_workflow_folderpath = self.app_workflow.folderpath
|
|
42
|
+
app_command_routing_definition = fastworkflow.RoutingRegistry.get_definition(app_workflow_folderpath)
|
|
43
|
+
|
|
44
|
+
command_parameters_class = (
|
|
45
|
+
app_command_routing_definition.get_command_class(
|
|
46
|
+
self.command_name, ModuleType.COMMAND_PARAMETERS_CLASS
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
if not command_parameters_class:
|
|
50
|
+
return self.Output(parameters_are_valid=True)
|
|
51
|
+
|
|
52
|
+
stored_params = self._get_stored_parameters(self.cme_workflow)
|
|
53
|
+
|
|
54
|
+
self.command = self.command.replace(self.command_name, "").strip()
|
|
55
|
+
|
|
56
|
+
input_for_param_extraction = InputForParamExtraction.create(
|
|
57
|
+
self.app_workflow, self.command_name,
|
|
58
|
+
self.command)
|
|
59
|
+
|
|
60
|
+
# If we have missing fields (in parameter extraction error state), try to apply the command directly
|
|
61
|
+
if stored_params:
|
|
62
|
+
new_params = self._extract_and_merge_missing_parameters(stored_params, self.command)
|
|
63
|
+
else:
|
|
64
|
+
# Check if we're in agentic mode (not assistant mode command)
|
|
65
|
+
is_agentic_mode = (
|
|
66
|
+
"is_assistant_mode_command" not in self.cme_workflow.context
|
|
67
|
+
and "run_as_agent" in self.app_workflow.context
|
|
68
|
+
and self.app_workflow.context["run_as_agent"]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if is_agentic_mode:
|
|
72
|
+
# Try regex-based extraction first in agentic mode
|
|
73
|
+
new_params = self._extract_parameters_from_xml(self.command, command_parameters_class)
|
|
74
|
+
|
|
75
|
+
# If regex extraction fails, fall back to LLM-based extraction
|
|
76
|
+
if new_params is None:
|
|
77
|
+
new_params = input_for_param_extraction.extract_parameters(
|
|
78
|
+
command_parameters_class,
|
|
79
|
+
self.command_name,
|
|
80
|
+
app_workflow_folderpath)
|
|
81
|
+
else:
|
|
82
|
+
# Use LLM-based extraction for assistant mode
|
|
83
|
+
new_params = input_for_param_extraction.extract_parameters(
|
|
84
|
+
command_parameters_class,
|
|
85
|
+
self.command_name,
|
|
86
|
+
app_workflow_folderpath)
|
|
87
|
+
|
|
88
|
+
is_valid, error_msg, suggestions, missing_invalid_fields = \
|
|
89
|
+
input_for_param_extraction.validate_parameters(
|
|
90
|
+
self.app_workflow, self.command_name, new_params
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Set all the missing and invalid fields to None before storing
|
|
94
|
+
current_values = {
|
|
95
|
+
field_name: getattr(new_params, field_name, None)
|
|
96
|
+
for field_name in list(type(new_params).model_fields.keys())
|
|
97
|
+
}
|
|
98
|
+
for field_name in missing_invalid_fields:
|
|
99
|
+
if field_name in current_values:
|
|
100
|
+
current_values[field_name] = NOT_FOUND
|
|
101
|
+
# Reconstruct the model instance without validation
|
|
102
|
+
new_params = new_params.__class__.model_construct(**current_values)
|
|
103
|
+
|
|
104
|
+
self._store_parameters(self.cme_workflow, new_params)
|
|
105
|
+
|
|
106
|
+
if not is_valid:
|
|
107
|
+
if params_str := self._format_parameters_for_display(new_params):
|
|
108
|
+
error_msg = f"Extracted parameters so far:\n{params_str}\n\n{error_msg}"
|
|
109
|
+
|
|
110
|
+
if "run_as_agent" not in self.app_workflow.context:
|
|
111
|
+
error_msg += "\nEnter 'abort' to get out of this error state and/or execute a different command."
|
|
112
|
+
error_msg += "\nEnter 'you misunderstood' if the wrong command was executed."
|
|
113
|
+
else:
|
|
114
|
+
error_msg += "\nCheck your command name if the wrong command was executed."
|
|
115
|
+
return self.Output(
|
|
116
|
+
parameters_are_valid=False,
|
|
117
|
+
error_msg=error_msg,
|
|
118
|
+
cmd_parameters=new_params,
|
|
119
|
+
suggestions=suggestions)
|
|
120
|
+
|
|
121
|
+
self._clear_parameters(self.cme_workflow)
|
|
122
|
+
return self.Output(
|
|
123
|
+
parameters_are_valid=True,
|
|
124
|
+
cmd_parameters=new_params)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _get_stored_parameters(cme_workflow: fastworkflow.Workflow):
|
|
128
|
+
return cme_workflow.context.get("stored_parameters")
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _store_parameters(cme_workflow: fastworkflow.Workflow, parameters):
|
|
132
|
+
cme_workflow.context["stored_parameters"] = parameters
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _clear_parameters(cme_workflow: fastworkflow.Workflow):
|
|
136
|
+
if "stored_parameters" in cme_workflow.context:
|
|
137
|
+
del cme_workflow.context["stored_parameters"]
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def _extract_missing_fields(input_for_param_extraction, sws, command_name, stored_params):
|
|
141
|
+
stored_missing_fields = []
|
|
142
|
+
is_valid, error_msg, _ = input_for_param_extraction.validate_parameters(
|
|
143
|
+
sws, command_name, stored_params
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if not is_valid:
|
|
147
|
+
if MISSING_INFORMATION_ERRMSG in error_msg:
|
|
148
|
+
missing_fields_str = error_msg.split(f"{MISSING_INFORMATION_ERRMSG}")[1].split("\n")[0]
|
|
149
|
+
stored_missing_fields = [f.strip() for f in missing_fields_str.split(",")]
|
|
150
|
+
if INVALID_INFORMATION_ERRMSG in error_msg:
|
|
151
|
+
invalid_section = error_msg.split(f"{INVALID_INFORMATION_ERRMSG}")[1]
|
|
152
|
+
if "\n" in invalid_section:
|
|
153
|
+
invalid_fields_str = invalid_section.split("\n")[0]
|
|
154
|
+
stored_missing_fields.extend(
|
|
155
|
+
invalid_field.split(" '")[0].strip()
|
|
156
|
+
for invalid_field in invalid_fields_str.split(", ")
|
|
157
|
+
)
|
|
158
|
+
return stored_missing_fields
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _merge_parameters(old_params, new_params, missing_fields):
|
|
162
|
+
"""
|
|
163
|
+
Merge new parameters with old parameters, prioritizing new values when appropriate.
|
|
164
|
+
"""
|
|
165
|
+
merged_data = {
|
|
166
|
+
field_name: getattr(old_params, field_name, None)
|
|
167
|
+
for field_name in list(type(old_params).model_fields.keys())
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# all_fields = list(old_params.model_fields.keys())
|
|
171
|
+
missing_fields = missing_fields or []
|
|
172
|
+
|
|
173
|
+
for field_name in missing_fields:
|
|
174
|
+
merged_data[field_name] = getattr(new_params, field_name)
|
|
175
|
+
|
|
176
|
+
# Construct the model instance without validation
|
|
177
|
+
return old_params.__class__.model_construct(**merged_data)
|
|
178
|
+
|
|
179
|
+
# if hasattr(new_params, field_name):
|
|
180
|
+
# new_value = getattr(new_params, field_name)
|
|
181
|
+
# old_value = merged_data.get(field_name)
|
|
182
|
+
|
|
183
|
+
# if new_value is not None and new_value != NOT_FOUND:
|
|
184
|
+
# if isinstance(old_value, str) and INVALID in old_value and INVALID not in new_value:
|
|
185
|
+
# merged_data[field_name] = new_value
|
|
186
|
+
|
|
187
|
+
# elif old_value is None or old_value == NOT_FOUND:
|
|
188
|
+
# merged_data[field_name] = new_value
|
|
189
|
+
|
|
190
|
+
# elif isinstance(old_value, int) and old_value == INVALID_INT_VALUE:
|
|
191
|
+
# with contextlib.suppress(ValueError, TypeError):
|
|
192
|
+
# merged_data[field_name] = int(new_value)
|
|
193
|
+
|
|
194
|
+
# elif isinstance(old_value, float) and old_value == INVALID_FLOAT_VALUE:
|
|
195
|
+
# with contextlib.suppress(ValueError, TypeError):
|
|
196
|
+
# merged_data[field_name] = float(new_value)
|
|
197
|
+
|
|
198
|
+
# elif (field_name in missing_fields and
|
|
199
|
+
# hasattr(old_params.model_fields.get(field_name), "json_schema_extra") and
|
|
200
|
+
# old_params.model_fields.get(field_name).json_schema_extra and
|
|
201
|
+
# "db_lookup" in old_params.model_fields.get(field_name).json_schema_extra):
|
|
202
|
+
# merged_data[field_name] = new_value
|
|
203
|
+
|
|
204
|
+
# elif field_name in missing_fields:
|
|
205
|
+
# field_info = old_params.model_fields.get(field_name)
|
|
206
|
+
# has_pattern = hasattr(field_info, "pattern") and field_info.pattern is not None
|
|
207
|
+
|
|
208
|
+
# if not has_pattern:
|
|
209
|
+
# for meta in getattr(field_info, "metadata", []):
|
|
210
|
+
# if hasattr(meta, "pattern"):
|
|
211
|
+
# has_pattern = True
|
|
212
|
+
# break
|
|
213
|
+
|
|
214
|
+
# if not has_pattern and hasattr(field_info, "json_schema_extra") and field_info.json_schema_extra:
|
|
215
|
+
# has_pattern = "pattern" in field_info.json_schema_extra
|
|
216
|
+
|
|
217
|
+
# if has_pattern:
|
|
218
|
+
# merged_data[field_name] = new_value
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _format_parameters_for_display(params):
|
|
222
|
+
"""
|
|
223
|
+
Format parameters for display in the error message.
|
|
224
|
+
"""
|
|
225
|
+
if not params:
|
|
226
|
+
return ""
|
|
227
|
+
|
|
228
|
+
lines = []
|
|
229
|
+
|
|
230
|
+
all_fields = list(type(params).model_fields.keys())
|
|
231
|
+
|
|
232
|
+
for field_name in all_fields:
|
|
233
|
+
value = getattr(params, field_name, None)
|
|
234
|
+
|
|
235
|
+
if value in [
|
|
236
|
+
NOT_FOUND,
|
|
237
|
+
None,
|
|
238
|
+
INVALID_INT_VALUE,
|
|
239
|
+
INVALID_FLOAT_VALUE
|
|
240
|
+
]:
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
display_name = " ".join(word.capitalize() for word in field_name.split('_'))
|
|
244
|
+
|
|
245
|
+
# Format fields appropriately based on type
|
|
246
|
+
if (
|
|
247
|
+
isinstance(value, bool)
|
|
248
|
+
or not hasattr(value, 'value')
|
|
249
|
+
and isinstance(value, (int, float))
|
|
250
|
+
or not hasattr(value, 'value')
|
|
251
|
+
and isinstance(value, str)
|
|
252
|
+
or not hasattr(value, 'value')
|
|
253
|
+
):
|
|
254
|
+
lines.append(f"{display_name}: {value}")
|
|
255
|
+
else: # Handle enum types
|
|
256
|
+
lines.append(f"{display_name}: {value.value}")
|
|
257
|
+
return "\n".join(lines)
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def _apply_missing_fields(command: str, default_params: BaseModel, missing_fields: list):
|
|
261
|
+
global PARAMETER_EXTRACTION_ERROR_MSG
|
|
262
|
+
if not PARAMETER_EXTRACTION_ERROR_MSG:
|
|
263
|
+
PARAMETER_EXTRACTION_ERROR_MSG = fastworkflow.get_env_var("PARAMETER_EXTRACTION_ERROR_MSG")
|
|
264
|
+
|
|
265
|
+
# Work on plain dict to avoid validation during assignment
|
|
266
|
+
params_data = {
|
|
267
|
+
field_name: getattr(default_params, field_name, None)
|
|
268
|
+
for field_name in list(type(default_params).model_fields.keys())
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if "," in command:
|
|
272
|
+
parts = [part.strip() for part in command.split(",")]
|
|
273
|
+
|
|
274
|
+
if (
|
|
275
|
+
len(parts) == len(missing_fields) == 1
|
|
276
|
+
or len(parts) != len(missing_fields)
|
|
277
|
+
and parts
|
|
278
|
+
and missing_fields
|
|
279
|
+
):
|
|
280
|
+
field = missing_fields[0]
|
|
281
|
+
if field in params_data:
|
|
282
|
+
params_data[field] = parts[0]
|
|
283
|
+
elif len(parts) == len(missing_fields) and len(missing_fields) > 1:
|
|
284
|
+
for i, field in enumerate(missing_fields):
|
|
285
|
+
if i < len(parts) and field in params_data:
|
|
286
|
+
params_data[field] = parts[i]
|
|
287
|
+
elif missing_fields:
|
|
288
|
+
field = missing_fields[0]
|
|
289
|
+
if field in params_data:
|
|
290
|
+
params_data[field] = command.strip()
|
|
291
|
+
|
|
292
|
+
# Construct model without validation
|
|
293
|
+
return default_params.__class__.model_construct(**params_data)
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _extract_parameters_from_xml(command: str, command_parameters_class: type[BaseModel]) -> Optional[BaseModel]:
|
|
297
|
+
"""
|
|
298
|
+
Extract parameters from XML-formatted command using regex.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
BaseModel instance with extracted parameters, or None if parsing fails
|
|
302
|
+
"""
|
|
303
|
+
field_names = list(command_parameters_class.model_fields.keys())
|
|
304
|
+
|
|
305
|
+
# If no parameters are defined, return empty model immediately
|
|
306
|
+
if not field_names:
|
|
307
|
+
return command_parameters_class.model_construct()
|
|
308
|
+
|
|
309
|
+
extracted_data = {}
|
|
310
|
+
|
|
311
|
+
# Try to extract each parameter using XML tags
|
|
312
|
+
if len(field_names) == 1:
|
|
313
|
+
# If there's only one field, extract content from first XML tag
|
|
314
|
+
pattern = r'<[^>]+>(.+?)</[^>]+>'
|
|
315
|
+
if match := re.search(pattern, command, re.DOTALL):
|
|
316
|
+
parameter_value = match[1].strip()
|
|
317
|
+
extracted_data[field_names[0]] = parameter_value
|
|
318
|
+
else:
|
|
319
|
+
# Try to extract each parameter using XML tags
|
|
320
|
+
for field_name in field_names:
|
|
321
|
+
# Look for <field_name>value</field_name> pattern
|
|
322
|
+
pattern = rf'<{re.escape(field_name)}>(.+?)</{re.escape(field_name)}>'
|
|
323
|
+
if match := re.search(pattern, command, re.DOTALL):
|
|
324
|
+
parameter_value = match[1].strip()
|
|
325
|
+
extracted_data[field_name] = parameter_value
|
|
326
|
+
|
|
327
|
+
# Check if we extracted values for ALL fields (safest criteria for LLM fallback)
|
|
328
|
+
all_fields_extracted = len(extracted_data) == len(field_names)
|
|
329
|
+
|
|
330
|
+
# Check if agent used example values
|
|
331
|
+
if all_fields_extracted:
|
|
332
|
+
for field_name, extracted_value in extracted_data.items():
|
|
333
|
+
field_info = command_parameters_class.model_fields[field_name]
|
|
334
|
+
examples = getattr(field_info, "examples", None)
|
|
335
|
+
if examples and extracted_value in examples:
|
|
336
|
+
all_fields_extracted = False
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
if all_fields_extracted:
|
|
340
|
+
# Initialize all fields with their default values (if they exist) or None
|
|
341
|
+
params_data = {}
|
|
342
|
+
for field_name in field_names:
|
|
343
|
+
field_info = command_parameters_class.model_fields[field_name]
|
|
344
|
+
if field_info.default is not PydanticUndefined:
|
|
345
|
+
params_data[field_name] = field_info.default
|
|
346
|
+
elif field_info.default_factory is not None:
|
|
347
|
+
params_data[field_name] = field_info.default_factory()
|
|
348
|
+
else:
|
|
349
|
+
params_data[field_name] = None
|
|
350
|
+
|
|
351
|
+
# Update with extracted values
|
|
352
|
+
params_data |= extracted_data
|
|
353
|
+
|
|
354
|
+
# Construct model without validation
|
|
355
|
+
return command_parameters_class.model_construct(**params_data)
|
|
356
|
+
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def _extract_and_merge_missing_parameters(stored_params: BaseModel, command: str):
|
|
361
|
+
"""
|
|
362
|
+
Identify fields to fill by scanning for sentinel values and merge values
|
|
363
|
+
parsed from the command string into a new params instance. This preserves
|
|
364
|
+
existing behavior for token/field count mismatches and leaves values as
|
|
365
|
+
strings (no type coercion).
|
|
366
|
+
"""
|
|
367
|
+
# Initialize with existing values to avoid triggering validation
|
|
368
|
+
field_names = list(type(stored_params).model_fields.keys())
|
|
369
|
+
params_data = {
|
|
370
|
+
field_name: getattr(stored_params, field_name, None)
|
|
371
|
+
for field_name in field_names
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
# Determine which fields still need user-provided input based on sentinels
|
|
375
|
+
fields_to_fill = []
|
|
376
|
+
for field_name in field_names:
|
|
377
|
+
value = getattr(stored_params, field_name, None)
|
|
378
|
+
if value in [
|
|
379
|
+
NOT_FOUND,
|
|
380
|
+
None,
|
|
381
|
+
INVALID_INT_VALUE,
|
|
382
|
+
INVALID_FLOAT_VALUE,
|
|
383
|
+
]:
|
|
384
|
+
fields_to_fill.append(field_name)
|
|
385
|
+
|
|
386
|
+
if not fields_to_fill:
|
|
387
|
+
return stored_params
|
|
388
|
+
|
|
389
|
+
# Preserve existing mismatch handling and keep all values as strings
|
|
390
|
+
if "," in command:
|
|
391
|
+
parts = [part.strip() for part in command.split(",")]
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
len(parts) == len(fields_to_fill) == 1
|
|
395
|
+
or len(parts) != len(fields_to_fill)
|
|
396
|
+
and parts
|
|
397
|
+
):
|
|
398
|
+
field = fields_to_fill[0]
|
|
399
|
+
if field in params_data:
|
|
400
|
+
params_data[field] = parts[0]
|
|
401
|
+
elif len(parts) == len(fields_to_fill) and len(fields_to_fill) > 1:
|
|
402
|
+
for i, field in enumerate(fields_to_fill):
|
|
403
|
+
if i < len(parts) and field in params_data:
|
|
404
|
+
params_data[field] = parts[i]
|
|
405
|
+
else:
|
|
406
|
+
field = fields_to_fill[0]
|
|
407
|
+
if field in params_data:
|
|
408
|
+
params_data[field] = command.strip()
|
|
409
|
+
|
|
410
|
+
# Return a new instance without validation
|
|
411
|
+
return stored_params.__class__.model_construct(**params_data)
|