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.
- ibm_watsonx_orchestrate/__init__.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -5
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +34 -3
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +11 -2
- ibm_watsonx_orchestrate/agent_builder/models/types.py +18 -1
- ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +14 -2
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +61 -1
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +6 -0
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +21 -3
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +11 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +29 -53
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -30
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +25 -2
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +249 -14
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +5 -1
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -3
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +3 -2
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +45 -16
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +23 -4
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +29 -10
- ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_controller.py +21 -4
- ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +7 -15
- ibm_watsonx_orchestrate/cli/commands/partners/partners_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +30 -20
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +139 -27
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +79 -36
- ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +23 -11
- ibm_watsonx_orchestrate/cli/common.py +26 -0
- ibm_watsonx_orchestrate/cli/config.py +33 -2
- ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +34 -1
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +6 -2
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +1 -1
- ibm_watsonx_orchestrate/client/models/models_client.py +1 -1
- ibm_watsonx_orchestrate/client/threads/threads_client.py +34 -0
- ibm_watsonx_orchestrate/client/utils.py +29 -7
- ibm_watsonx_orchestrate/docker/compose-lite.yml +58 -8
- ibm_watsonx_orchestrate/docker/default.env +26 -17
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +10 -2
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +90 -16
- ibm_watsonx_orchestrate/flow_builder/node.py +14 -2
- ibm_watsonx_orchestrate/flow_builder/types.py +57 -3
- ibm_watsonx_orchestrate/langflow/__init__.py +0 -0
- ibm_watsonx_orchestrate/langflow/langflow_utils.py +195 -0
- ibm_watsonx_orchestrate/langflow/lfx_deps.py +84 -0
- ibm_watsonx_orchestrate/utils/async_helpers.py +31 -0
- ibm_watsonx_orchestrate/utils/docker_utils.py +1177 -33
- ibm_watsonx_orchestrate/utils/environment.py +165 -20
- ibm_watsonx_orchestrate/utils/exceptions.py +1 -1
- ibm_watsonx_orchestrate/utils/tokens.py +51 -0
- ibm_watsonx_orchestrate/utils/utils.py +63 -4
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/RECORD +66 -59
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/entry_points.txt +0 -0
- {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,
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
+
|