ibm-watsonx-orchestrate 1.12.0b0__py3-none-any.whl → 1.13.0b0__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.
Files changed (66) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -5
  3. ibm_watsonx_orchestrate/agent_builder/connections/types.py +34 -3
  4. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +11 -2
  5. ibm_watsonx_orchestrate/agent_builder/models/types.py +18 -1
  6. ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +1 -1
  7. ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +14 -2
  8. ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -1
  9. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +1 -1
  10. ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +61 -1
  11. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +6 -0
  12. ibm_watsonx_orchestrate/agent_builder/tools/types.py +21 -3
  13. ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -1
  14. ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +11 -0
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +29 -53
  16. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
  17. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -30
  18. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +25 -2
  19. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +249 -14
  20. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
  21. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +5 -1
  22. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -3
  23. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +3 -2
  24. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +1 -1
  25. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +45 -16
  26. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +23 -4
  27. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +2 -2
  28. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +29 -10
  29. ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_controller.py +21 -4
  30. ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +7 -15
  31. ibm_watsonx_orchestrate/cli/commands/partners/partners_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +30 -20
  33. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +2 -2
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +139 -27
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -2
  36. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +79 -36
  37. ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +23 -11
  38. ibm_watsonx_orchestrate/cli/common.py +26 -0
  39. ibm_watsonx_orchestrate/cli/config.py +33 -2
  40. ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
  41. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +34 -1
  42. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +6 -2
  43. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +1 -1
  44. ibm_watsonx_orchestrate/client/models/models_client.py +1 -1
  45. ibm_watsonx_orchestrate/client/threads/threads_client.py +34 -0
  46. ibm_watsonx_orchestrate/client/utils.py +29 -7
  47. ibm_watsonx_orchestrate/docker/compose-lite.yml +58 -8
  48. ibm_watsonx_orchestrate/docker/default.env +26 -17
  49. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +10 -2
  50. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +90 -16
  51. ibm_watsonx_orchestrate/flow_builder/node.py +14 -2
  52. ibm_watsonx_orchestrate/flow_builder/types.py +57 -3
  53. ibm_watsonx_orchestrate/langflow/__init__.py +0 -0
  54. ibm_watsonx_orchestrate/langflow/langflow_utils.py +195 -0
  55. ibm_watsonx_orchestrate/langflow/lfx_deps.py +84 -0
  56. ibm_watsonx_orchestrate/utils/async_helpers.py +31 -0
  57. ibm_watsonx_orchestrate/utils/docker_utils.py +1177 -33
  58. ibm_watsonx_orchestrate/utils/environment.py +165 -20
  59. ibm_watsonx_orchestrate/utils/exceptions.py +1 -1
  60. ibm_watsonx_orchestrate/utils/tokens.py +51 -0
  61. ibm_watsonx_orchestrate/utils/utils.py +63 -4
  62. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/METADATA +2 -2
  63. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/RECORD +66 -59
  64. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/WHEEL +0 -0
  65. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/entry_points.txt +0 -0
  66. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -21,17 +21,20 @@ from typing_extensions import Self
21
21
  from pydantic import BaseModel, Field, SerializeAsAny, create_model, TypeAdapter
22
22
  import yaml
23
23
  from ibm_watsonx_orchestrate.agent_builder.tools.python_tool import PythonTool
24
+ from ibm_watsonx_orchestrate.agent_builder.models.types import ListVirtualModel
24
25
  from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
25
26
  from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
26
27
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
27
28
  from ..types import (
28
- DocProcKVPSchema, Assignment, Conditions, EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy, NodeIdCondition, PlainTextReadingOrder, PromptExample, PromptLLMParameters, PromptNodeSpec, TimerNodeSpec,
29
+ DocProcKVPSchema, Assignment, Conditions, EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy,
30
+ NodeIdCondition, PlainTextReadingOrder, PromptExample, PromptLLMParameters, PromptNodeSpec, ScriptNodeSpec, TimerNodeSpec,
31
+ NodeErrorHandlerConfig, NodeIdCondition, PlainTextReadingOrder, PromptExample, PromptLLMParameters, PromptNodeSpec,
29
32
  StartNodeSpec, ToolSpec, JsonSchemaObject, ToolRequestBody, ToolResponseBody, UserFieldKind, UserFieldOption, UserFlowSpec, UserNodeSpec, WaitPolicy,
30
33
  DocProcSpec, TextExtractionResponse, DocProcInput, DecisionsNodeSpec, DecisionsRule, DocExtSpec, File, DocumentClassificationResponse, DocClassifierSpec, DocumentProcessingCommonInput
31
34
  )
32
35
  from .constants import CURRENT_USER, START, END, ANY_USER
33
36
  from ..node import (
34
- EndNode, Node, PromptNode, StartNode, UserNode, AgentNode, DataMap, ToolNode, DocProcNode, DecisionsNode, DocExtNode, DocClassifierNode
37
+ EndNode, Node, PromptNode, ScriptNode, StartNode, UserNode, AgentNode, DataMap, ToolNode, DocProcNode, DecisionsNode, DocExtNode, DocClassifierNode
35
38
  )
36
39
  from ..types import (
37
40
  AgentNodeSpec, extract_node_spec, FlowContext, FlowEventType, FlowEvent, FlowSpec,
@@ -84,6 +87,21 @@ class Flow(Node):
84
87
  # get Tool Client
85
88
  self._tool_client = instantiate_client(ToolClient)
86
89
 
90
+ # set llm_model to use for the flow if any
91
+ llm_model = kwargs.get("llm_model")
92
+ if llm_model:
93
+ if isinstance(llm_model, ListVirtualModel):
94
+ self.metadata["llm_model"] = llm_model.name
95
+ elif isinstance(llm_model, str):
96
+ self.metadata["llm_model"] = llm_model
97
+ else:
98
+ raise AssertionError(f"flow llm_model should be either a str or ListVirtualModel")
99
+
100
+ # set agent_conversation_memory_turns_limit for the flow if any
101
+ agent_conversation_memory_turns_limit = kwargs.get("agent_conversation_memory_turns_limit")
102
+ if agent_conversation_memory_turns_limit:
103
+ self.metadata["agent_conversation_memory_turns_limit"] = agent_conversation_memory_turns_limit
104
+
87
105
  def _find_topmost_flow(self) -> Self:
88
106
  if self.parent:
89
107
  return self.parent._find_topmost_flow()
@@ -253,7 +271,8 @@ class Flow(Node):
253
271
 
254
272
  def _create_node_from_tool_fn(
255
273
  self,
256
- tool: Callable
274
+ tool: Callable,
275
+ error_handler_config: Optional[NodeErrorHandlerConfig] = None
257
276
  ) -> ToolNode:
258
277
  if not isinstance(tool, Callable):
259
278
  raise ValueError("Only functions with @tool decorator can be added.")
@@ -276,7 +295,8 @@ class Flow(Node):
276
295
  input_schema = tool_spec.input_schema,
277
296
  output_schema = tool_spec.output_schema,
278
297
  output_schema_object = spec.output_schema_object,
279
- tool = tool_spec.name)
298
+ tool = tool_spec.name,
299
+ error_handler_config = error_handler_config,)
280
300
 
281
301
  return ToolNode(spec=toolnode_spec)
282
302
 
@@ -286,14 +306,18 @@ class Flow(Node):
286
306
  name: str | None = None,
287
307
  display_name: str | None = None,
288
308
  description: str | None = None,
289
-
290
309
  input_schema: type[BaseModel] | None = None,
291
310
  output_schema: type[BaseModel] | None = None,
292
- input_map: DataMap = None
311
+ input_map: DataMap = None,
312
+ error_handler_config: NodeErrorHandlerConfig | None = None
293
313
  ) -> ToolNode:
294
314
  '''create a tool node in the flow'''
295
315
  if tool is None:
296
316
  raise ValueError("tool must be provided")
317
+
318
+
319
+ if isinstance(error_handler_config, dict):
320
+ error_handler_config = NodeErrorHandlerConfig.model_validate(error_handler_config)
297
321
 
298
322
  if isinstance(tool, str):
299
323
  name = name if name is not None and name != "" else tool
@@ -322,14 +346,16 @@ class Flow(Node):
322
346
  input_schema= _get_tool_request_body(input_schema_obj),
323
347
  output_schema= _get_tool_response_body(output_schema_obj),
324
348
  output_schema_object = output_schema_obj,
325
- tool = tool)
349
+ tool = tool,
350
+ error_handler_config = error_handler_config
351
+ )
326
352
 
327
353
  node = ToolNode(spec=toolnode_spec)
328
354
  elif isinstance(tool, PythonTool):
329
355
  if callable(tool):
330
356
  tool_spec = getattr(tool, "__tool_spec__", None)
331
357
  if tool_spec:
332
- node = self._create_node_from_tool_fn(tool)
358
+ node = self._create_node_from_tool_fn(tool, error_handler_config = error_handler_config)
333
359
  else:
334
360
  raise ValueError("Only functions with @tool decorator can be added.")
335
361
  else:
@@ -341,6 +367,41 @@ class Flow(Node):
341
367
 
342
368
  node = self._add_node(node)
343
369
  return cast(ToolNode, node)
370
+
371
+
372
+ def script(
373
+ self,
374
+ script: str | None = "",
375
+ name: str | None = None,
376
+ display_name: str | None = None,
377
+ description: str | None = None,
378
+ input_schema: type[BaseModel] | None = None,
379
+ output_schema: type[BaseModel] | None = None,
380
+ input_map: DataMap = None
381
+ ) -> ScriptNode:
382
+ '''create a script node in the flow'''
383
+ name = name if name is not None and name != "" else ""
384
+
385
+ input_schema_obj = _get_json_schema_obj("input", input_schema)
386
+ output_schema_obj = _get_json_schema_obj("output", output_schema)
387
+
388
+ script_node_spec = ScriptNodeSpec(
389
+ name = name,
390
+ display_name = display_name,
391
+ description = description,
392
+ input_schema= _get_tool_request_body(input_schema_obj),
393
+ output_schema= _get_tool_response_body(output_schema_obj),
394
+ output_schema_object = output_schema_obj,
395
+ fn = script)
396
+
397
+ node = ScriptNode(spec=script_node_spec)
398
+
399
+ # setup input and output map
400
+ if input_map:
401
+ node.input_map = self._get_data_map(input_map)
402
+
403
+ node = self._add_node(node)
404
+ return cast(ScriptNode, node)
344
405
 
345
406
 
346
407
  def _add_node(self, node: Node) -> Node:
@@ -407,7 +468,8 @@ class Flow(Node):
407
468
  description: str | None = None,
408
469
  input_schema: type[BaseModel]|None = None,
409
470
  output_schema: type[BaseModel]|None=None,
410
- input_map: DataMap = None) -> PromptNode:
471
+ input_map: DataMap = None,
472
+ error_handler_config: NodeErrorHandlerConfig | None = None,) -> PromptNode:
411
473
 
412
474
  if name is None:
413
475
  raise ValueError("name must be provided.")
@@ -426,6 +488,7 @@ class Flow(Node):
426
488
  prompt_examples=prompt_examples,
427
489
  llm=llm,
428
490
  llm_parameters=llm_parameters,
491
+ error_handler_config=error_handler_config,
429
492
  input_schema=_get_tool_request_body(input_schema_obj),
430
493
  output_schema=_get_tool_response_body(output_schema_obj),
431
494
  output_schema_object = output_schema_obj
@@ -519,7 +582,9 @@ class Flow(Node):
519
582
  fields: type[BaseModel]| None = None,
520
583
  description: str | None = None,
521
584
  input_map: DataMap = None,
522
- enable_hw: bool = False) -> tuple[DocExtNode, type[BaseModel]]:
585
+ enable_hw: bool = False,
586
+ min_confidence: float = 0, # Setting a small value because htil is not supported for pro code.
587
+ review_fields: List[str] = []) -> tuple[DocExtNode, type[BaseModel]]:
523
588
 
524
589
  if name is None :
525
590
  raise ValueError("name must be provided.")
@@ -544,7 +609,9 @@ class Flow(Node):
544
609
  output_schema_object = output_schema_obj,
545
610
  config=doc_ext_config,
546
611
  version=version,
547
- enable_hw=enable_hw
612
+ enable_hw=enable_hw,
613
+ min_confidence=min_confidence,
614
+ review_fields=review_fields
548
615
  )
549
616
  node = DocExtNode(spec=task_spec)
550
617
 
@@ -609,7 +676,8 @@ class Flow(Node):
609
676
  input_map: DataMap = None,
610
677
  document_structure: bool = False,
611
678
  kvp_schemas: list[DocProcKVPSchema] = None,
612
- enable_hw: bool = False) -> DocProcNode:
679
+ enable_hw: bool = False,
680
+ kvp_model_name: str | None = None) -> DocProcNode:
613
681
 
614
682
  if name is None :
615
683
  raise ValueError("name must be provided.")
@@ -635,7 +703,8 @@ class Flow(Node):
635
703
  document_structure=document_structure,
636
704
  plain_text_reading_order=plain_text_reading_order,
637
705
  enable_hw=enable_hw,
638
- kvp_schemas=kvp_schemas
706
+ kvp_schemas=kvp_schemas,
707
+ kvp_model_name=kvp_model_name
639
708
  )
640
709
 
641
710
  node = DocProcNode(spec=task_spec)
@@ -1201,7 +1270,7 @@ class CompiledFlow(BaseModel):
1201
1270
  dumped = self.flow.to_json()
1202
1271
  with open(file, 'w') as f:
1203
1272
  if file.endswith(".yaml") or file.endswith(".yml"):
1204
- yaml.dump(dumped, f)
1273
+ yaml.dump(dumped, f, allow_unicode=True)
1205
1274
  elif file.endswith(".json"):
1206
1275
  json.dump(dumped, f, indent=2)
1207
1276
  else:
@@ -1223,7 +1292,10 @@ class FlowFactory(BaseModel):
1223
1292
  initiators: Sequence[str]|None=None,
1224
1293
  input_schema: type[BaseModel]|None=None,
1225
1294
  output_schema: type[BaseModel]|None=None,
1226
- schedulable: bool=False) -> Flow:
1295
+ private_schema: type[BaseModel]|None=None,
1296
+ schedulable: bool=False,
1297
+ llm_model: str|ListVirtualModel|None=None,
1298
+ agent_conversation_memory_turns_limit: int|None = None) -> Flow:
1227
1299
  if isinstance(name, Callable):
1228
1300
  flow_spec = getattr(name, "__flow_spec__", None)
1229
1301
  if not flow_spec:
@@ -1233,6 +1305,7 @@ class FlowFactory(BaseModel):
1233
1305
  input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = input_schema)
1234
1306
  # create input spec
1235
1307
  output_schema_obj = _get_json_schema_obj("output", output_schema)
1308
+ private_schema_obj = _get_json_schema_obj("private", private_schema)
1236
1309
  if initiators is None:
1237
1310
  initiators = []
1238
1311
 
@@ -1244,11 +1317,12 @@ class FlowFactory(BaseModel):
1244
1317
  initiators=initiators,
1245
1318
  input_schema=_get_tool_request_body(input_schema_obj),
1246
1319
  output_schema=_get_tool_response_body(output_schema_obj),
1320
+ private_schema = private_schema_obj,
1247
1321
  output_schema_object = output_schema_obj,
1248
1322
  schedulable=schedulable,
1249
1323
  )
1250
1324
 
1251
- return Flow(spec = flow_spec)
1325
+ return Flow(spec = flow_spec, llm_model=llm_model, agent_conversation_memory_turns_limit=agent_conversation_memory_turns_limit)
1252
1326
 
1253
1327
 
1254
1328
  class FlowControl(Node):
@@ -6,7 +6,7 @@ import yaml
6
6
  from pydantic import BaseModel, Field, SerializeAsAny, create_model
7
7
  from enum import Enum
8
8
 
9
- from .types import Assignment, DocExtConfigField, EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, TimerNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec, DocProcSpec, \
9
+ from .types import Assignment, DocExtConfigField, EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, ScriptNodeSpec, TimerNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec, DocProcSpec, \
10
10
  DocExtSpec, DocExtConfig, DocClassifierSpec, DecisionsNodeSpec, DocClassifierConfig
11
11
 
12
12
  from .data_map import DataMap
@@ -23,7 +23,7 @@ class Node(BaseModel):
23
23
  exclude_unset=True, exclude_none=True, by_alias=True)
24
24
  with open(file, 'w', encoding="utf-8") as f:
25
25
  if file.endswith('.yaml') or file.endswith('.yml'):
26
- yaml.dump(dumped, f)
26
+ yaml.dump(dumped, f, allow_unicode=True)
27
27
  elif file.endswith('.json'):
28
28
  json.dump(dumped, f, indent=2)
29
29
  else:
@@ -132,6 +132,18 @@ class ToolNode(Node):
132
132
 
133
133
  def get_spec(self) -> ToolNodeSpec:
134
134
  return cast(ToolNodeSpec, self.spec)
135
+
136
+
137
+ class ScriptNode(Node):
138
+ def __repr__(self):
139
+ return f"ScriptNode(name='{self.spec.name}', description='{self.spec.description}')"
140
+
141
+ def get_spec(self) -> ScriptNodeSpec:
142
+ return cast(ScriptNodeSpec, self.spec)
143
+
144
+ def updateScript(self, script: str):
145
+ '''Update the script of a script node'''
146
+ self.spec.fn = script
135
147
 
136
148
  class UserNode(Node):
137
149
  def __repr__(self):
@@ -271,6 +271,8 @@ class DocClassifierSpec(DocProcCommonNodeSpec):
271
271
  class DocExtSpec(DocProcCommonNodeSpec):
272
272
  version : str = Field(description="A version of the spec")
273
273
  config : DocExtConfig
274
+ min_confidence: float = Field(description="The minimal confidence acceptable for an extracted field value", default=0.0,le=1.0, ge=0.0 ,title="Minimum Confidence")
275
+ review_fields: List[str] = Field(description="The fields that require user to review", default=[])
274
276
 
275
277
  def __init__(self, **data):
276
278
  super().__init__(**data)
@@ -281,6 +283,8 @@ class DocExtSpec(DocProcCommonNodeSpec):
281
283
  model_spec["version"] = self.version
282
284
  model_spec["config"] = self.config.model_dump()
283
285
  model_spec["task"] = DocProcTask.custom_field_extraction
286
+ model_spec["min_confidence"] = self.min_confidence
287
+ model_spec["review_fields"] = self.review_fields
284
288
  return model_spec
285
289
 
286
290
  class DocProcField(BaseModel):
@@ -337,6 +341,11 @@ class DocProcSpec(DocProcCommonNodeSpec):
337
341
  title='KVP schemas',
338
342
  description="Optional list of key-value pair schemas to use for extraction.",
339
343
  default=None)
344
+ kvp_model_name: str | None = Field(
345
+ title='KVP Model Name',
346
+ description="The LLM model to be used for key-value pair extraction",
347
+ default=None
348
+ )
340
349
  plain_text_reading_order : PlainTextReadingOrder = Field(default=PlainTextReadingOrder.block_structure)
341
350
  document_structure: bool = Field(default=False,description="Requests the entire document structure computed by WDU to be returned")
342
351
 
@@ -352,6 +361,8 @@ class DocProcSpec(DocProcCommonNodeSpec):
352
361
  model_spec["plain_text_reading_order"] = self.plain_text_reading_order
353
362
  if self.kvp_schemas is not None:
354
363
  model_spec["kvp_schemas"] = self.kvp_schemas
364
+ if self.kvp_model_name is not None:
365
+ model_spec["kvp_model_name"] = self.kvp_model_name
355
366
  return model_spec
356
367
 
357
368
  class StartNodeSpec(NodeSpec):
@@ -363,8 +374,24 @@ class EndNodeSpec(NodeSpec):
363
374
  def __init__(self, **data):
364
375
  super().__init__(**data)
365
376
  self.kind = "end"
377
+
378
+ class NodeErrorHandlerConfig(BaseModel):
379
+ error_message: Optional[str] = None
380
+ max_retries: Optional[int] = None
381
+ retry_interval: Optional[int] = None
382
+
383
+ def to_json(self) -> dict[str, Any]:
384
+ model_spec = {}
385
+ if self.error_message:
386
+ model_spec["error_message"] = self.error_message
387
+ if self.max_retries:
388
+ model_spec["max_retries"] = self.max_retries
389
+ if self.retry_interval:
390
+ model_spec["retry_interval"] = self.retry_interval
391
+ return model_spec
366
392
  class ToolNodeSpec(NodeSpec):
367
393
  tool: Union[str, ToolSpec] = Field(default = None, description="the tool to use")
394
+ error_handler_config: Optional[NodeErrorHandlerConfig] = None
368
395
 
369
396
  def __init__(self, **data):
370
397
  super().__init__(**data)
@@ -372,12 +399,27 @@ class ToolNodeSpec(NodeSpec):
372
399
 
373
400
  def to_json(self) -> dict[str, Any]:
374
401
  model_spec = super().to_json()
402
+ if self.error_handler_config:
403
+ model_spec["error_handler_config"] = self.error_handler_config.to_json()
375
404
  if self.tool:
376
405
  if isinstance(self.tool, ToolSpec):
377
406
  model_spec["tool"] = self.tool.model_dump(exclude_defaults=True, exclude_none=True, exclude_unset=True)
378
407
  else:
379
408
  model_spec["tool"] = self.tool
380
409
  return model_spec
410
+
411
+ class ScriptNodeSpec(NodeSpec):
412
+ fn: str = Field(default = None, description="the script to execute")
413
+
414
+ def __init__(self, **data):
415
+ super().__init__(**data)
416
+ self.kind = "script"
417
+
418
+ def to_json(self) -> dict[str, Any]:
419
+ model_spec = super().to_json()
420
+ if self.fn:
421
+ model_spec["fn"] = self.fn
422
+ return model_spec
381
423
 
382
424
 
383
425
  class UserFieldValue(BaseModel):
@@ -708,6 +750,7 @@ class PromptNodeSpec(NodeSpec):
708
750
  prompt_examples: Optional[list[PromptExample]]
709
751
  llm: Optional[str]
710
752
  llm_parameters: Optional[PromptLLMParameters]
753
+ error_handler_config: Optional[NodeErrorHandlerConfig]
711
754
 
712
755
  def __init__(self, **kwargs):
713
756
  super().__init__(**kwargs)
@@ -723,6 +766,8 @@ class PromptNodeSpec(NodeSpec):
723
766
  model_spec["llm"] = self.llm
724
767
  if self.llm_parameters:
725
768
  model_spec["llm_parameters"] = self.llm_parameters.to_json()
769
+ if self.error_handler_config:
770
+ model_spec["error_handler_config"] = self.error_handler_config.to_json()
726
771
  if self.prompt_examples:
727
772
  model_spec["prompt_examples"] = []
728
773
  for example in self.prompt_examples:
@@ -860,6 +905,9 @@ class FlowSpec(NodeSpec):
860
905
  initiators: Sequence[str] = [ANY_USER]
861
906
  schedulable: bool = False
862
907
 
908
+ # flow can have private schema
909
+ private_schema: JsonSchemaObject | SchemaRef | None = None
910
+
863
911
  def __init__(self, **kwargs):
864
912
  super().__init__(**kwargs)
865
913
  self.kind = "flow"
@@ -868,6 +916,8 @@ class FlowSpec(NodeSpec):
868
916
  model_spec = super().to_json()
869
917
  if self.initiators:
870
918
  model_spec["initiators"] = self.initiators
919
+ if self.private_schema:
920
+ model_spec["private_schema"] = _to_json_from_json_schema(self.private_schema)
871
921
 
872
922
  model_spec["schedulable"] = self.schedulable
873
923
 
@@ -905,8 +955,7 @@ class UserFlowSpec(FlowSpec):
905
955
  class ForeachPolicy(Enum):
906
956
 
907
957
  SEQUENTIAL = 1
908
- # support only SEQUENTIAL for now
909
- # PARALLEL = 2
958
+ PARALLEL = 2
910
959
 
911
960
  class ForeachSpec(FlowSpec):
912
961
 
@@ -923,7 +972,7 @@ class ForeachSpec(FlowSpec):
923
972
  if isinstance(self.item_schema, JsonSchemaObject):
924
973
  my_dict["item_schema"] = _to_json_from_json_schema(self.item_schema)
925
974
  else:
926
- my_dict["item_schema"] = self.item_schema.model_dump(exclude_defaults=True, exclude_none=True, exclude_unset=True)
975
+ my_dict["item_schema"] = self.item_schema.model_dump(exclude_defaults=True, exclude_none=True, exclude_unset=True, by_alias=True)
927
976
 
928
977
  my_dict["foreach_policy"] = self.foreach_policy.name
929
978
  return my_dict
@@ -1312,6 +1361,11 @@ class DocProcInput(DocumentProcessingCommonInput):
1312
1361
  title='KVP schemas',
1313
1362
  description="Optional list of key-value pair schemas to use for extraction.",
1314
1363
  default=None)
1364
+ kvp_model_name: str | None = Field(
1365
+ title='KVP Model Name',
1366
+ description="The LLM model to be used for key-value pair extraction",
1367
+ default=None
1368
+ )
1315
1369
 
1316
1370
  class TextExtractionResponse(BaseModel):
1317
1371
  '''
File without changes
@@ -0,0 +1,195 @@
1
+ import logging
2
+ import ast
3
+ import sys
4
+ from pathlib import Path
5
+ import importlib.util
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from .lfx_deps import LFX_DEPENDENCIES
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class LangflowComponent(BaseModel):
14
+ id: str
15
+ name: str
16
+ credentials: dict
17
+ requirements: list[str] = []
18
+
19
+ class LangflowModelSpec(BaseModel):
20
+ version: str
21
+ components: list[LangflowComponent]
22
+
23
+ _MODULE_MAP = {
24
+ "mem0":"mem0ai",
25
+ }
26
+
27
+ import math
28
+ from collections import Counter
29
+
30
+ def _calculate_entropy(s):
31
+ """
32
+ Calculates the Shannon entropy of a string.
33
+
34
+ Parameters:
35
+ s (str): Input string.
36
+
37
+ Returns:
38
+ float: Shannon entropy value.
39
+ """
40
+ if not s:
41
+ return 0.0
42
+
43
+ freq = Counter(s)
44
+ length = len(s)
45
+
46
+ entropy = -sum((count / length) * math.log2(count / length) for count in freq.values())
47
+ return entropy
48
+
49
+ def _mask_api_key(key):
50
+ """
51
+ Masks an API key by keeping the first 5 characters visible,
52
+ masking the rest with asterisks, and truncating the result to a maximum of 25 characters.
53
+
54
+ Parameters:
55
+ key (str): The API key string.
56
+
57
+ Returns:
58
+ str: Masked and truncated API key.
59
+ """
60
+ if not isinstance(key, str):
61
+ return key
62
+
63
+ # if this is a potential real API key -- mask it
64
+ if _calculate_entropy(key) > 4.1:
65
+ visible_part = key[:5]
66
+ masked_part = '*' * (len(key) - 5)
67
+ masked_key = visible_part + masked_part
68
+
69
+ return masked_key[:25]
70
+ elif len(key) > 25:
71
+ # if the key is longer than 25 characters, truncates it anyway
72
+ return key[:22] + '...'
73
+
74
+ return key
75
+
76
+ def _extract_imports(source_code) -> list[str]:
77
+ tree = ast.parse(source_code)
78
+ imports = set()
79
+ for node in ast.walk(tree):
80
+ if isinstance(node, ast.Import):
81
+ for alias in node.names:
82
+ # we only need the module name, not sub-module
83
+ imports.add(alias.name.split('.')[0])
84
+ elif isinstance(node, ast.ImportFrom):
85
+ if node.module:
86
+ # we only need the module name, not sub-module
87
+ imports.add(node.module.split('.')[0])
88
+ return sorted(imports)
89
+
90
+
91
+
92
+ def _is_builtin_module(module_name: str) -> bool:
93
+ underscore_module_name = f"_{module_name}"
94
+
95
+ # Check against the list of standard modules
96
+ if module_name in sys.stdlib_module_names:
97
+ return True
98
+
99
+ if underscore_module_name in sys.stdlib_module_names:
100
+ return True
101
+
102
+ # Check against the list of built-in module names
103
+ if module_name in sys.builtin_module_names:
104
+ return True
105
+
106
+ if underscore_module_name in sys.builtin_module_names:
107
+ return True
108
+
109
+ # Use importlib to find the module spec
110
+ spec = importlib.util.find_spec(module_name)
111
+ if spec is None:
112
+ return False # Module not found
113
+
114
+ # Check if the loader is a BuiltinImporter
115
+ return isinstance(spec.loader, importlib.machinery.BuiltinImporter)
116
+
117
+
118
+ def _find_missing_requirements(imported_modules, requirements_modules: list[str]) -> list[str]:
119
+ """
120
+ Compare imported modules with requirements.txt and return missing ones.
121
+
122
+ Parameters:
123
+ imported_modules (list): List of module names used in the code.
124
+ requirements_file_path (str): Path to the requirements.txt file.
125
+
126
+ Returns:
127
+ list: Modules that are imported but not listed in requirements.txt.
128
+ """
129
+ def normalize_module_name(name):
130
+ module_name = name.split('.')[0].lower()
131
+ # sometimes the module name in pipy is different than the real name
132
+ if module_name in _MODULE_MAP:
133
+ module_name = _MODULE_MAP[module_name]
134
+ return module_name
135
+
136
+ # Normalize imported module names
137
+ normalized_imports = [normalize_module_name(mod) for mod in imported_modules]
138
+
139
+ # filter out all built-ins
140
+ filtered_imports = [
141
+ module for module in normalized_imports
142
+ if _is_builtin_module(module) is False
143
+ ]
144
+
145
+ # Compare and find missing modules
146
+ missing_modules = [
147
+ module for module in filtered_imports
148
+ if module not in requirements_modules
149
+ ]
150
+
151
+ return missing_modules
152
+
153
+
154
+
155
+ def parse_langflow_model(model) -> LangflowModelSpec:
156
+ """
157
+ Extracts component details and Langflow version from a Langflow JSON object.
158
+
159
+ Parameters:
160
+ model (dict): The Langflow JSON object.
161
+
162
+ Returns:
163
+ LangflowModelSpec: A LangflowModelSpec object containing the extracted version and component information.
164
+ """
165
+ version = model.get("last_tested_version", "Unknown")
166
+ components = []
167
+ data = model.get('data', {} )
168
+
169
+ # get the list of available modules
170
+ requirements_modules = LFX_DEPENDENCIES
171
+
172
+ for node in data.get("nodes", []):
173
+ node_data = node.get("data", {})
174
+ node_info = node_data.get("node", {})
175
+ template = node_info.get("template", {})
176
+ code = template.get("code")
177
+ credentials = {}
178
+
179
+ missing_imports = []
180
+ for field_name, field_info in template.items():
181
+ if isinstance(field_info, dict) and field_info.get("password", False) == True:
182
+ credentials[field_name] = _mask_api_key(field_info.get("value"))
183
+
184
+ if code and code.get("value") != None:
185
+ imports = _extract_imports(code.get("value"))
186
+ if len(imports) > 0:
187
+ missing_imports = _find_missing_requirements(imports, requirements_modules)
188
+
189
+ component_info = LangflowComponent(name=node_info.get("display_name", "Unknown"), id=node_data.get("id", "Unknown"),
190
+ credentials=credentials, requirements=missing_imports)
191
+
192
+ components.append(component_info)
193
+
194
+ return LangflowModelSpec(version=version, components=components)
195
+